@exaudeus/workrail 3.9.2 → 3.11.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 (64) hide show
  1. package/dist/application/services/validation-engine.js +134 -0
  2. package/dist/application/services/workflow-compiler.js +54 -0
  3. package/dist/manifest.json +150 -78
  4. package/dist/mcp/handlers/v2-advance-core/assessment-consequences.d.ts +14 -0
  5. package/dist/mcp/handlers/v2-advance-core/assessment-consequences.js +27 -0
  6. package/dist/mcp/handlers/v2-advance-core/assessment-validation.d.ts +16 -0
  7. package/dist/mcp/handlers/v2-advance-core/assessment-validation.js +213 -0
  8. package/dist/mcp/handlers/v2-advance-core/event-builders.d.ts +1 -0
  9. package/dist/mcp/handlers/v2-advance-core/event-builders.js +3 -2
  10. package/dist/mcp/handlers/v2-advance-core/index.js +23 -8
  11. package/dist/mcp/handlers/v2-advance-core/input-validation.d.ts +9 -1
  12. package/dist/mcp/handlers/v2-advance-core/input-validation.js +22 -2
  13. package/dist/mcp/handlers/v2-advance-core/outcome-blocked.d.ts +2 -0
  14. package/dist/mcp/handlers/v2-advance-core/outcome-blocked.js +69 -19
  15. package/dist/mcp/handlers/v2-advance-core/outcome-success.js +22 -0
  16. package/dist/mcp/handlers/v2-execution/replay.js +7 -0
  17. package/dist/mcp/output-schemas.d.ts +156 -42
  18. package/dist/mcp/output-schemas.js +17 -1
  19. package/dist/mcp/v2-response-formatter.js +7 -1
  20. package/dist/types/workflow-definition.d.ts +26 -0
  21. package/dist/types/workflow-definition.js +16 -1
  22. package/dist/v2/durable-core/constants.d.ts +4 -1
  23. package/dist/v2/durable-core/constants.js +5 -2
  24. package/dist/v2/durable-core/domain/assessment-consequence-event-builder.d.ts +23 -0
  25. package/dist/v2/durable-core/domain/assessment-consequence-event-builder.js +36 -0
  26. package/dist/v2/durable-core/domain/assessment-record.d.ts +12 -0
  27. package/dist/v2/durable-core/domain/assessment-record.js +2 -0
  28. package/dist/v2/durable-core/domain/assessment-recorded-event-builder.d.ts +22 -0
  29. package/dist/v2/durable-core/domain/assessment-recorded-event-builder.js +38 -0
  30. package/dist/v2/durable-core/domain/blocked-node-builder.d.ts +1 -1
  31. package/dist/v2/durable-core/domain/blocked-node-builder.js +8 -0
  32. package/dist/v2/durable-core/domain/blocking-decision.d.ts +6 -0
  33. package/dist/v2/durable-core/domain/blocking-decision.js +15 -0
  34. package/dist/v2/durable-core/domain/prompt-renderer.js +53 -67
  35. package/dist/v2/durable-core/domain/reason-model.d.ts +12 -2
  36. package/dist/v2/durable-core/domain/reason-model.js +27 -2
  37. package/dist/v2/durable-core/domain/retrieval-contract.d.ts +105 -0
  38. package/dist/v2/durable-core/domain/retrieval-contract.js +310 -0
  39. package/dist/v2/durable-core/domain/risk-policy-guardrails.js +1 -0
  40. package/dist/v2/durable-core/domain/validation-criteria-validator.d.ts +1 -0
  41. package/dist/v2/durable-core/domain/validation-criteria-validator.js +11 -0
  42. package/dist/v2/durable-core/schemas/artifacts/assessment.d.ts +55 -0
  43. package/dist/v2/durable-core/schemas/artifacts/assessment.js +29 -0
  44. package/dist/v2/durable-core/schemas/artifacts/index.d.ts +2 -1
  45. package/dist/v2/durable-core/schemas/artifacts/index.js +8 -1
  46. package/dist/v2/durable-core/schemas/compiled-workflow/index.d.ts +24 -24
  47. package/dist/v2/durable-core/schemas/execution-snapshot/blocked-snapshot.d.ts +141 -21
  48. package/dist/v2/durable-core/schemas/execution-snapshot/blocked-snapshot.js +10 -1
  49. package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.d.ts +729 -171
  50. package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +1442 -202
  51. package/dist/v2/durable-core/schemas/session/events.d.ts +231 -8
  52. package/dist/v2/durable-core/schemas/session/events.js +36 -0
  53. package/dist/v2/durable-core/schemas/session/gaps.d.ts +2 -2
  54. package/dist/v2/projections/assessment-consequences.d.ts +19 -0
  55. package/dist/v2/projections/assessment-consequences.js +33 -0
  56. package/dist/v2/projections/assessments.d.ts +21 -0
  57. package/dist/v2/projections/assessments.js +35 -0
  58. package/dist/v2/projections/resume-ranking.d.ts +1 -0
  59. package/dist/v2/projections/resume-ranking.js +12 -24
  60. package/package.json +1 -1
  61. package/spec/workflow.schema.json +110 -0
  62. package/workflows/bug-investigation.agentic.v2.json +28 -2
  63. package/workflows/mr-review-workflow.agentic.v2.json +10 -5
  64. package/workflows/test-artifact-loop-control.json +28 -2
@@ -5,6 +5,7 @@ exports.toPendingStep = toPendingStep;
5
5
  const zod_1 = require("zod");
6
6
  const state_js_1 = require("../domain/execution/state.js");
7
7
  const token_patterns_js_1 = require("../v2/durable-core/tokens/token-patterns.js");
8
+ const constants_js_1 = require("../v2/durable-core/constants.js");
8
9
  const JsonPrimitiveSchema = zod_1.z.union([zod_1.z.string(), zod_1.z.number(), zod_1.z.boolean(), zod_1.z.null()]);
9
10
  exports.JsonValueSchema = zod_1.z.lazy(() => zod_1.z.union([JsonPrimitiveSchema, zod_1.z.array(exports.JsonValueSchema), zod_1.z.record(exports.JsonValueSchema)]));
10
11
  exports.WorkflowSummarySchema = zod_1.z.object({
@@ -143,6 +144,11 @@ const V2BlockerPointerSchema = zod_1.z.discriminatedUnion('kind', [
143
144
  zod_1.z.object({ kind: zod_1.z.literal('context_budget') }),
144
145
  zod_1.z.object({ kind: zod_1.z.literal('output_contract'), contractRef: zod_1.z.string().min(1) }),
145
146
  zod_1.z.object({ kind: zod_1.z.literal('capability'), capability: zod_1.z.enum(['delegation', 'web_browsing']) }),
147
+ zod_1.z.object({
148
+ kind: zod_1.z.literal('assessment_dimension'),
149
+ assessmentId: zod_1.z.string().min(1),
150
+ dimensionId: zod_1.z.string().min(1).regex(DELIMITER_SAFE_ID_PATTERN, 'dimensionId must be delimiter-safe: [a-z0-9_-]+'),
151
+ }),
146
152
  zod_1.z.object({
147
153
  kind: zod_1.z.literal('workflow_step'),
148
154
  stepId: zod_1.z.string().min(1).regex(DELIMITER_SAFE_ID_PATTERN, 'stepId must be delimiter-safe: [a-z0-9_-]+'),
@@ -153,6 +159,7 @@ const V2BlockerSchema = zod_1.z.object({
153
159
  'USER_ONLY_DEPENDENCY',
154
160
  'MISSING_REQUIRED_OUTPUT',
155
161
  'INVALID_REQUIRED_OUTPUT',
162
+ 'ASSESSMENT_FOLLOWUP_REQUIRED',
156
163
  'MISSING_REQUIRED_NOTES',
157
164
  'MISSING_CONTEXT_KEY',
158
165
  'CONTEXT_BUDGET_EXCEEDED',
@@ -194,6 +201,9 @@ exports.V2BlockerReportSchema = zod_1.z
194
201
  case 'capability':
195
202
  ptrStable = p.capability;
196
203
  break;
204
+ case 'assessment_dimension':
205
+ ptrStable = `${p.assessmentId}|${p.dimensionId}`;
206
+ break;
197
207
  case 'workflow_step':
198
208
  ptrStable = p.stepId;
199
209
  break;
@@ -251,6 +261,12 @@ const V2ContinueWorkflowBlockedSchema = zod_1.z.object({
251
261
  suggestions: zod_1.z.array(zod_1.z.string()),
252
262
  })
253
263
  .optional(),
264
+ assessmentFollowup: zod_1.z
265
+ .object({
266
+ title: zod_1.z.string().min(1),
267
+ guidance: zod_1.z.string().min(1),
268
+ })
269
+ .optional(),
254
270
  });
255
271
  exports.V2ContinueWorkflowOutputSchema = zod_1.z.discriminatedUnion('kind', [
256
272
  V2ContinueWorkflowOkSchema,
@@ -264,7 +280,7 @@ exports.V2ResumeSessionOutputSchema = zod_1.z.object({
264
280
  sessionTitle: zod_1.z.string().nullable().describe('Human-readable task/session title derived from persisted workflow context or early recap text.'),
265
281
  gitBranch: zod_1.z.string().nullable().describe('Git branch associated with the session, if available.'),
266
282
  resumeToken: zod_1.z.string().regex(token_patterns_js_1.STATE_TOKEN_PATTERN, 'Invalid resumeToken format'),
267
- snippet: zod_1.z.string().max(1024),
283
+ snippet: zod_1.z.string().max(constants_js_1.MAX_RESUME_PREVIEW_BYTES),
268
284
  confidence: zod_1.z.enum(['strong', 'medium', 'weak']).describe('Coarse confidence band for how likely this candidate is the intended session.'),
269
285
  matchExplanation: zod_1.z.string().min(1).describe('Short natural-language explanation of why this candidate ranked here.'),
270
286
  pendingStepId: zod_1.z.string().nullable().describe('The current pending step ID (e.g. "phase-3-implement") if the workflow is in progress. ' +
@@ -126,7 +126,13 @@ function formatBlockedRetryable(data) {
126
126
  lines.push('');
127
127
  }
128
128
  }
129
- lines.push('Retry with corrected output:');
129
+ if (data.assessmentFollowup) {
130
+ lines.push('**Follow-up required before retrying this step:**');
131
+ lines.push(`- ${data.assessmentFollowup.title}`);
132
+ lines.push(`- ${data.assessmentFollowup.guidance}`);
133
+ lines.push('');
134
+ }
135
+ lines.push('Retry the same step with improved output:');
130
136
  lines.push('');
131
137
  lines.push(formatTokenBlock(data));
132
138
  return lines.join('\n');
@@ -11,6 +11,29 @@ export interface PromptFragment {
11
11
  readonly when?: Condition;
12
12
  readonly text: string;
13
13
  }
14
+ export interface AssessmentDimensionDefinition {
15
+ readonly id: string;
16
+ readonly purpose: string;
17
+ readonly levels: readonly string[];
18
+ readonly required?: boolean;
19
+ }
20
+ export interface AssessmentDefinition {
21
+ readonly id: string;
22
+ readonly purpose: string;
23
+ readonly dimensions: readonly AssessmentDimensionDefinition[];
24
+ }
25
+ export interface AssessmentConsequenceTriggerDefinition {
26
+ readonly dimensionId: string;
27
+ readonly equalsLevel: string;
28
+ }
29
+ export interface AssessmentFollowupRequiredEffectDefinition {
30
+ readonly kind: 'require_followup';
31
+ readonly guidance: string;
32
+ }
33
+ export interface AssessmentConsequenceDefinition {
34
+ readonly when: AssessmentConsequenceTriggerDefinition;
35
+ readonly effect: AssessmentFollowupRequiredEffectDefinition;
36
+ }
14
37
  export interface WorkflowStepDefinition {
15
38
  readonly id: string;
16
39
  readonly title: string;
@@ -23,6 +46,8 @@ export interface WorkflowStepDefinition {
23
46
  readonly runCondition?: Readonly<Record<string, unknown>>;
24
47
  readonly validationCriteria?: ValidationCriteria;
25
48
  readonly outputContract?: OutputContract;
49
+ readonly assessmentRefs?: readonly string[];
50
+ readonly assessmentConsequences?: readonly AssessmentConsequenceDefinition[];
26
51
  readonly notesOptional?: boolean;
27
52
  readonly templateCall?: TemplateCall;
28
53
  readonly promptFragments?: readonly PromptFragment[];
@@ -106,6 +131,7 @@ export interface WorkflowDefinition {
106
131
  readonly functionDefinitions?: readonly FunctionDefinition[];
107
132
  readonly recommendedPreferences?: WorkflowRecommendedPreferences;
108
133
  readonly features?: readonly string[];
134
+ readonly assessments?: readonly AssessmentDefinition[];
109
135
  readonly extensionPoints?: readonly ExtensionPoint[];
110
136
  readonly references?: readonly WorkflowReference[];
111
137
  }
@@ -28,11 +28,26 @@ function hasWorkflowDefinitionShape(obj) {
28
28
  function createWorkflowDefinition(definition) {
29
29
  return Object.freeze({
30
30
  ...definition,
31
- steps: Object.freeze(definition.steps.map(step => Object.freeze({ ...step }))),
31
+ steps: Object.freeze(definition.steps.map(step => Object.freeze({
32
+ ...step,
33
+ assessmentConsequences: 'assessmentConsequences' in step && step.assessmentConsequences
34
+ ? Object.freeze(step.assessmentConsequences.map(consequence => Object.freeze({
35
+ ...consequence,
36
+ when: Object.freeze({ ...consequence.when }),
37
+ effect: Object.freeze({ ...consequence.effect }),
38
+ })))
39
+ : undefined,
40
+ }))),
32
41
  preconditions: definition.preconditions ? Object.freeze([...definition.preconditions]) : undefined,
33
42
  clarificationPrompts: definition.clarificationPrompts ? Object.freeze([...definition.clarificationPrompts]) : undefined,
34
43
  metaGuidance: definition.metaGuidance ? Object.freeze([...definition.metaGuidance]) : undefined,
35
44
  functionDefinitions: definition.functionDefinitions ? Object.freeze([...definition.functionDefinitions]) : undefined,
45
+ assessments: definition.assessments
46
+ ? Object.freeze(definition.assessments.map(assessment => Object.freeze({
47
+ ...assessment,
48
+ dimensions: Object.freeze(assessment.dimensions.map(dimension => Object.freeze({ ...dimension }))),
49
+ })))
50
+ : undefined,
36
51
  extensionPoints: definition.extensionPoints ? Object.freeze([...definition.extensionPoints]) : undefined,
37
52
  references: definition.references ? Object.freeze(definition.references.map(ref => Object.freeze({ ...ref }))) : undefined,
38
53
  });
@@ -15,7 +15,8 @@ export declare const MAX_OBSERVATION_SHORT_STRING_LENGTH = 80;
15
15
  export declare const SESSION_LOCK_RETRY_AFTER_MS = 1000;
16
16
  export declare const DEFAULT_RETRY_AFTER_MS = 1000;
17
17
  export declare const TRUNCATION_MARKER = "\n\n[TRUNCATED]";
18
- export declare const RECOVERY_BUDGET_BYTES = 12288;
18
+ export declare const RECOVERY_BUDGET_BYTES: number;
19
+ export declare const MAX_RESUME_PREVIEW_BYTES: number;
19
20
  export declare const SHA256_DIGEST_PATTERN: RegExp;
20
21
  export declare const DELIMITER_SAFE_ID_PATTERN: RegExp;
21
22
  export declare const EVENT_KIND: {
@@ -27,6 +28,8 @@ export declare const EVENT_KIND: {
27
28
  readonly ADVANCE_RECORDED: "advance_recorded";
28
29
  readonly VALIDATION_PERFORMED: "validation_performed";
29
30
  readonly NODE_OUTPUT_APPENDED: "node_output_appended";
31
+ readonly ASSESSMENT_RECORDED: "assessment_recorded";
32
+ readonly ASSESSMENT_CONSEQUENCE_APPLIED: "assessment_consequence_applied";
30
33
  readonly PREFERENCES_CHANGED: "preferences_changed";
31
34
  readonly CAPABILITY_OBSERVED: "capability_observed";
32
35
  readonly GAP_RECORDED: "gap_recorded";
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AUTONOMY_MODE = exports.ADVANCE_INTENT = exports.MANIFEST_KIND = exports.EDGE_CAUSE = exports.ADVANCE_OUTCOME = exports.ENGINE_STATE = exports.EDGE_KIND = exports.PAYLOAD_KIND = exports.OUTPUT_CHANNEL = exports.EVENT_KIND = exports.DELIMITER_SAFE_ID_PATTERN = exports.SHA256_DIGEST_PATTERN = exports.RECOVERY_BUDGET_BYTES = exports.TRUNCATION_MARKER = exports.DEFAULT_RETRY_AFTER_MS = exports.SESSION_LOCK_RETRY_AFTER_MS = exports.MAX_OBSERVATION_SHORT_STRING_LENGTH = exports.MAX_CONTEXT_DEPTH = exports.MAX_CONTEXT_BYTES = exports.MAX_VALIDATION_SUGGESTIONS_BYTES = exports.MAX_VALIDATION_ISSUES_BYTES = exports.MAX_VALIDATION_SUGGESTION_ITEM_BYTES = exports.MAX_VALIDATION_ISSUE_ITEM_BYTES = exports.MAX_OUTPUT_NOTES_MARKDOWN_BYTES = exports.MAX_DECISION_TRACE_TOTAL_BYTES = exports.MAX_DECISION_TRACE_ENTRY_SUMMARY_BYTES = exports.MAX_DECISION_TRACE_ENTRIES = exports.MAX_BLOCKER_SUGGESTED_FIX_BYTES = exports.MAX_BLOCKER_MESSAGE_BYTES = exports.MAX_BLOCKERS = void 0;
3
+ exports.AUTONOMY_MODE = exports.ADVANCE_INTENT = exports.MANIFEST_KIND = exports.EDGE_CAUSE = exports.ADVANCE_OUTCOME = exports.ENGINE_STATE = exports.EDGE_KIND = exports.PAYLOAD_KIND = exports.OUTPUT_CHANNEL = exports.EVENT_KIND = exports.DELIMITER_SAFE_ID_PATTERN = exports.SHA256_DIGEST_PATTERN = exports.MAX_RESUME_PREVIEW_BYTES = exports.RECOVERY_BUDGET_BYTES = exports.TRUNCATION_MARKER = exports.DEFAULT_RETRY_AFTER_MS = exports.SESSION_LOCK_RETRY_AFTER_MS = exports.MAX_OBSERVATION_SHORT_STRING_LENGTH = exports.MAX_CONTEXT_DEPTH = exports.MAX_CONTEXT_BYTES = exports.MAX_VALIDATION_SUGGESTIONS_BYTES = exports.MAX_VALIDATION_ISSUES_BYTES = exports.MAX_VALIDATION_SUGGESTION_ITEM_BYTES = exports.MAX_VALIDATION_ISSUE_ITEM_BYTES = exports.MAX_OUTPUT_NOTES_MARKDOWN_BYTES = exports.MAX_DECISION_TRACE_TOTAL_BYTES = exports.MAX_DECISION_TRACE_ENTRY_SUMMARY_BYTES = exports.MAX_DECISION_TRACE_ENTRIES = exports.MAX_BLOCKER_SUGGESTED_FIX_BYTES = exports.MAX_BLOCKER_MESSAGE_BYTES = exports.MAX_BLOCKERS = void 0;
4
4
  exports.MAX_BLOCKERS = 10;
5
5
  exports.MAX_BLOCKER_MESSAGE_BYTES = 512;
6
6
  exports.MAX_BLOCKER_SUGGESTED_FIX_BYTES = 1024;
@@ -18,7 +18,8 @@ exports.MAX_OBSERVATION_SHORT_STRING_LENGTH = 80;
18
18
  exports.SESSION_LOCK_RETRY_AFTER_MS = 1000;
19
19
  exports.DEFAULT_RETRY_AFTER_MS = 1000;
20
20
  exports.TRUNCATION_MARKER = '\n\n[TRUNCATED]';
21
- exports.RECOVERY_BUDGET_BYTES = 12288;
21
+ exports.RECOVERY_BUDGET_BYTES = 24 * 1024;
22
+ exports.MAX_RESUME_PREVIEW_BYTES = 2 * 1024;
22
23
  exports.SHA256_DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/;
23
24
  exports.DELIMITER_SAFE_ID_PATTERN = /^[a-z0-9_-]+$/;
24
25
  exports.EVENT_KIND = {
@@ -30,6 +31,8 @@ exports.EVENT_KIND = {
30
31
  ADVANCE_RECORDED: 'advance_recorded',
31
32
  VALIDATION_PERFORMED: 'validation_performed',
32
33
  NODE_OUTPUT_APPENDED: 'node_output_appended',
34
+ ASSESSMENT_RECORDED: 'assessment_recorded',
35
+ ASSESSMENT_CONSEQUENCE_APPLIED: 'assessment_consequence_applied',
33
36
  PREFERENCES_CHANGED: 'preferences_changed',
34
37
  CAPABILITY_OBSERVED: 'capability_observed',
35
38
  GAP_RECORDED: 'gap_recorded',
@@ -0,0 +1,23 @@
1
+ import { type Result } from 'neverthrow';
2
+ import type { DomainEventV1 } from '../schemas/session/index.js';
3
+ type EventToAppendV1 = Omit<DomainEventV1, 'eventIndex' | 'sessionId'>;
4
+ export type AssessmentConsequenceEventError = {
5
+ readonly code: 'ASSESSMENT_CONSEQUENCE_EVENT_INVARIANT_VIOLATION';
6
+ readonly message: string;
7
+ };
8
+ export declare function buildAssessmentConsequenceAppliedEvent(args: {
9
+ readonly sessionId: string;
10
+ readonly attemptId: string;
11
+ readonly scope: {
12
+ readonly runId: string;
13
+ readonly nodeId: string;
14
+ };
15
+ readonly assessmentId: string;
16
+ readonly dimensionId: string;
17
+ readonly level: string;
18
+ readonly guidance: string;
19
+ readonly minted: {
20
+ readonly eventId: string;
21
+ };
22
+ }): Result<EventToAppendV1, AssessmentConsequenceEventError>;
23
+ export {};
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildAssessmentConsequenceAppliedEvent = buildAssessmentConsequenceAppliedEvent;
4
+ const neverthrow_1 = require("neverthrow");
5
+ const constants_js_1 = require("../constants.js");
6
+ function buildAssessmentConsequenceAppliedEvent(args) {
7
+ if (!args.sessionId)
8
+ return (0, neverthrow_1.err)({ code: 'ASSESSMENT_CONSEQUENCE_EVENT_INVARIANT_VIOLATION', message: 'sessionId is required' });
9
+ if (!args.attemptId)
10
+ return (0, neverthrow_1.err)({ code: 'ASSESSMENT_CONSEQUENCE_EVENT_INVARIANT_VIOLATION', message: 'attemptId is required' });
11
+ if (!args.scope.runId || !args.scope.nodeId) {
12
+ return (0, neverthrow_1.err)({ code: 'ASSESSMENT_CONSEQUENCE_EVENT_INVARIANT_VIOLATION', message: 'scope.runId and scope.nodeId are required' });
13
+ }
14
+ if (!args.assessmentId || !args.dimensionId || !args.level || !args.guidance) {
15
+ return (0, neverthrow_1.err)({ code: 'ASSESSMENT_CONSEQUENCE_EVENT_INVARIANT_VIOLATION', message: 'assessment consequence fields are required' });
16
+ }
17
+ return (0, neverthrow_1.ok)({
18
+ v: 1,
19
+ eventId: args.minted.eventId,
20
+ kind: constants_js_1.EVENT_KIND.ASSESSMENT_CONSEQUENCE_APPLIED,
21
+ dedupeKey: `assessment_consequence_applied:${args.sessionId}:${args.scope.nodeId}:${args.attemptId}`,
22
+ scope: { runId: args.scope.runId, nodeId: args.scope.nodeId },
23
+ data: {
24
+ attemptId: args.attemptId,
25
+ assessmentId: args.assessmentId,
26
+ trigger: {
27
+ dimensionId: args.dimensionId,
28
+ level: args.level,
29
+ },
30
+ effect: {
31
+ kind: 'require_followup',
32
+ guidance: args.guidance,
33
+ },
34
+ },
35
+ });
36
+ }
@@ -0,0 +1,12 @@
1
+ export interface RecordedAssessmentDimensionV1 {
2
+ readonly dimensionId: string;
3
+ readonly level: string;
4
+ readonly rationale?: string;
5
+ readonly normalization: 'exact' | 'normalized';
6
+ }
7
+ export interface RecordedAssessmentV1 {
8
+ readonly assessmentId: string;
9
+ readonly summary?: string;
10
+ readonly dimensions: readonly RecordedAssessmentDimensionV1[];
11
+ readonly normalizationNotes: readonly string[];
12
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,22 @@
1
+ import { type Result } from 'neverthrow';
2
+ import type { DomainEventV1 } from '../schemas/session/index.js';
3
+ import type { RecordedAssessmentV1 } from './assessment-record.js';
4
+ type EventToAppendV1 = Omit<DomainEventV1, 'eventIndex' | 'sessionId'>;
5
+ export type AssessmentRecordedEventError = {
6
+ readonly code: 'ASSESSMENT_EVENT_INVARIANT_VIOLATION';
7
+ readonly message: string;
8
+ };
9
+ export declare function buildAssessmentRecordedEvent(args: {
10
+ readonly sessionId: string;
11
+ readonly attemptId: string;
12
+ readonly artifactOutputId: string;
13
+ readonly scope: {
14
+ readonly runId: string;
15
+ readonly nodeId: string;
16
+ };
17
+ readonly assessment: RecordedAssessmentV1;
18
+ readonly minted: {
19
+ readonly eventId: string;
20
+ };
21
+ }): Result<EventToAppendV1, AssessmentRecordedEventError>;
22
+ export {};
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildAssessmentRecordedEvent = buildAssessmentRecordedEvent;
4
+ const neverthrow_1 = require("neverthrow");
5
+ const constants_js_1 = require("../constants.js");
6
+ function buildAssessmentRecordedEvent(args) {
7
+ if (!args.sessionId)
8
+ return (0, neverthrow_1.err)({ code: 'ASSESSMENT_EVENT_INVARIANT_VIOLATION', message: 'sessionId is required' });
9
+ if (!args.attemptId)
10
+ return (0, neverthrow_1.err)({ code: 'ASSESSMENT_EVENT_INVARIANT_VIOLATION', message: 'attemptId is required' });
11
+ if (!args.artifactOutputId)
12
+ return (0, neverthrow_1.err)({ code: 'ASSESSMENT_EVENT_INVARIANT_VIOLATION', message: 'artifactOutputId is required' });
13
+ if (!args.scope.runId || !args.scope.nodeId) {
14
+ return (0, neverthrow_1.err)({ code: 'ASSESSMENT_EVENT_INVARIANT_VIOLATION', message: 'scope.runId and scope.nodeId are required' });
15
+ }
16
+ if (!args.minted.eventId)
17
+ return (0, neverthrow_1.err)({ code: 'ASSESSMENT_EVENT_INVARIANT_VIOLATION', message: 'minted.eventId is required' });
18
+ return (0, neverthrow_1.ok)({
19
+ v: 1,
20
+ eventId: args.minted.eventId,
21
+ kind: constants_js_1.EVENT_KIND.ASSESSMENT_RECORDED,
22
+ dedupeKey: `assessment_recorded:${args.sessionId}:${args.scope.nodeId}:${args.attemptId}`,
23
+ scope: { runId: args.scope.runId, nodeId: args.scope.nodeId },
24
+ data: {
25
+ assessmentId: args.assessment.assessmentId,
26
+ attemptId: args.attemptId,
27
+ artifactOutputId: args.artifactOutputId,
28
+ summary: args.assessment.summary,
29
+ normalizationNotes: [...args.assessment.normalizationNotes],
30
+ dimensions: args.assessment.dimensions.map((dimension) => ({
31
+ dimensionId: dimension.dimensionId,
32
+ level: dimension.level,
33
+ normalization: dimension.normalization,
34
+ ...(dimension.rationale ? { rationale: dimension.rationale } : {}),
35
+ })),
36
+ },
37
+ });
38
+ }
@@ -14,7 +14,7 @@ export declare function buildBlockedNodeSnapshot(args: {
14
14
  readonly priorSnapshot: ExecutionSnapshotFileV1;
15
15
  readonly primaryReason: ReasonV1;
16
16
  readonly attemptId: AttemptId;
17
- readonly validationRef: string;
17
+ readonly validationRef?: string;
18
18
  readonly blockers: BlockerReportV1;
19
19
  readonly sha256: Sha256PortV2;
20
20
  }): Result<ExecutionSnapshotFileV1, BlockedNodeBuildError>;
@@ -9,6 +9,14 @@ function toContractViolationReason(reason) {
9
9
  return { kind: 'invalid_required_output', contractRef: reason.contractRef };
10
10
  case 'missing_required_output':
11
11
  return { kind: 'missing_required_output', contractRef: reason.contractRef };
12
+ case 'assessment_followup_required':
13
+ return {
14
+ kind: 'assessment_followup_required',
15
+ assessmentId: reason.assessmentId,
16
+ dimensionId: reason.dimensionId,
17
+ level: reason.level,
18
+ guidance: reason.guidance,
19
+ };
12
20
  case 'missing_context_key':
13
21
  return { kind: 'missing_context_key', key: reason.key };
14
22
  case 'context_budget_exceeded':
@@ -34,4 +34,10 @@ export declare function detectBlockingReasonsV1(args: {
34
34
  readonly missingNotes?: {
35
35
  readonly stepId: string;
36
36
  };
37
+ readonly assessmentFollowupRequired?: {
38
+ readonly assessmentId: string;
39
+ readonly dimensionId: string;
40
+ readonly level: string;
41
+ readonly guidance: string;
42
+ };
37
43
  }): Result<readonly ReasonV1[], BlockingDecisionError>;
@@ -63,5 +63,20 @@ function detectBlockingReasonsV1(args) {
63
63
  }
64
64
  reasons.push({ kind: 'missing_notes', stepId: args.missingNotes.stepId });
65
65
  }
66
+ if (args.assessmentFollowupRequired) {
67
+ if (!constants_js_1.DELIMITER_SAFE_ID_PATTERN.test(args.assessmentFollowupRequired.dimensionId)) {
68
+ return (0, neverthrow_1.err)({
69
+ code: 'INVALID_DELIMITER_SAFE_ID',
70
+ message: `assessment dimension ID must be delimiter-safe: [a-z0-9_-]+ (got: ${args.assessmentFollowupRequired.dimensionId})`,
71
+ });
72
+ }
73
+ reasons.push({
74
+ kind: 'assessment_followup_required',
75
+ assessmentId: args.assessmentFollowupRequired.assessmentId,
76
+ dimensionId: args.assessmentFollowupRequired.dimensionId,
77
+ level: args.assessmentFollowupRequired.level,
78
+ guidance: args.assessmentFollowupRequired.guidance,
79
+ });
80
+ }
66
81
  return (0, neverthrow_1.ok)(reasons);
67
82
  }
@@ -15,11 +15,13 @@ const index_js_2 = require("../schemas/artifacts/index.js");
15
15
  const run_context_js_1 = require("../../projections/run-context.js");
16
16
  const condition_evaluator_js_1 = require("../../../utils/condition-evaluator.js");
17
17
  const context_template_resolver_js_1 = require("./context-template-resolver.js");
18
- function buildNonTipSections(args) {
19
- const sections = [];
18
+ const retrieval_contract_js_1 = require("./retrieval-contract.js");
19
+ function buildNonTipSegments(args) {
20
+ const segments = [];
20
21
  const childSummary = (0, recap_recovery_js_1.buildChildSummary)({ nodeId: args.nodeId, dag: args.run });
21
- if (childSummary) {
22
- sections.push(`### Branch Summary\n${childSummary}`);
22
+ const childSummarySegment = (0, retrieval_contract_js_1.createBranchSummarySegment)(childSummary);
23
+ if (childSummarySegment) {
24
+ segments.push(childSummarySegment);
23
25
  }
24
26
  if (args.run.preferredTipNodeId && args.run.preferredTipNodeId !== String(args.nodeId)) {
25
27
  const downstreamRes = (0, recap_recovery_js_1.collectDownstreamRecap)({
@@ -29,12 +31,15 @@ function buildNonTipSections(args) {
29
31
  outputs: args.outputs,
30
32
  });
31
33
  if (downstreamRes.isOk() && downstreamRes.value.length > 0) {
32
- sections.push(`### Downstream Recap (Preferred Branch)\n${downstreamRes.value.join('\n\n')}`);
34
+ const downstreamSegment = (0, retrieval_contract_js_1.createDownstreamRecapSegment)(downstreamRes.value.join('\n\n'));
35
+ if (downstreamSegment) {
36
+ segments.push(downstreamSegment);
37
+ }
33
38
  }
34
39
  }
35
- return sections;
40
+ return segments;
36
41
  }
37
- function buildAncestrySections(args) {
42
+ function buildAncestrySegments(args) {
38
43
  const ancestryRes = (0, recap_recovery_js_1.collectAncestryRecap)({
39
44
  nodeId: args.nodeId,
40
45
  dag: args.run,
@@ -42,11 +47,12 @@ function buildAncestrySections(args) {
42
47
  includeCurrentNode: false,
43
48
  });
44
49
  if (ancestryRes.isOk() && ancestryRes.value.length > 0) {
45
- return [`### Ancestry Recap\n${ancestryRes.value.join('\n\n')}`];
50
+ const ancestrySegment = (0, retrieval_contract_js_1.createAncestryRecapSegment)(ancestryRes.value.join('\n\n'));
51
+ return ancestrySegment ? [ancestrySegment] : [];
46
52
  }
47
53
  return [];
48
54
  }
49
- function buildFunctionDefsSections(args) {
55
+ function buildFunctionDefsSegments(args) {
50
56
  const funcsRes = (0, function_definition_expander_js_1.expandFunctionDefinitions)({
51
57
  workflow: args.workflow,
52
58
  stepId: args.stepId,
@@ -55,7 +61,8 @@ function buildFunctionDefsSections(args) {
55
61
  });
56
62
  if (funcsRes.isOk() && funcsRes.value.length > 0) {
57
63
  const formatted = funcsRes.value.map(function_definition_expander_js_1.formatFunctionDef).join('\n\n');
58
- return [`### Function Definitions\n\`\`\`\n${formatted}\n\`\`\``];
64
+ const functionDefinitionsSegment = (0, retrieval_contract_js_1.createFunctionDefinitionsSegment)(`\`\`\`\n${formatted}\n\`\`\``);
65
+ return functionDefinitionsSegment ? [functionDefinitionsSegment] : [];
59
66
  }
60
67
  return [];
61
68
  }
@@ -65,12 +72,12 @@ function hasPriorNotesInRun(args) {
65
72
  e.data.outputChannel === constants_js_1.OUTPUT_CHANNEL.RECAP &&
66
73
  e.data.payload.payloadKind === constants_js_1.PAYLOAD_KIND.NOTES);
67
74
  }
68
- function buildRecoverySections(args) {
75
+ function buildRecoverySegments(args) {
69
76
  const isTip = args.run.tipNodeIds.includes(String(args.nodeId));
70
77
  return [
71
- ...(isTip ? [] : buildNonTipSections({ nodeId: args.nodeId, run: args.run, outputs: args.outputs })),
72
- ...buildAncestrySections({ nodeId: args.nodeId, run: args.run, outputs: args.outputs }),
73
- ...buildFunctionDefsSections({
78
+ ...(isTip ? [] : buildNonTipSegments({ nodeId: args.nodeId, run: args.run, outputs: args.outputs })),
79
+ ...buildAncestrySegments({ nodeId: args.nodeId, run: args.run, outputs: args.outputs }),
80
+ ...buildFunctionDefsSegments({
74
81
  workflow: args.workflow,
75
82
  stepId: args.stepId,
76
83
  loopPath: args.loopPath,
@@ -78,52 +85,6 @@ function buildRecoverySections(args) {
78
85
  }),
79
86
  ];
80
87
  }
81
- function trimToUtf8Boundary(bytes) {
82
- const n = bytes.length;
83
- if (n === 0)
84
- return bytes;
85
- let cont = 0;
86
- for (let i = n - 1; i >= 0 && i >= n - 4; i--) {
87
- const b = bytes[i];
88
- if ((b & 192) === 128) {
89
- cont++;
90
- }
91
- else {
92
- break;
93
- }
94
- }
95
- if (cont === 0)
96
- return bytes;
97
- const leadByteIndex = n - cont - 1;
98
- if (leadByteIndex < 0) {
99
- return new Uint8Array(0);
100
- }
101
- const leadByte = bytes[leadByteIndex];
102
- const expectedLen = (leadByte & 128) === 0 ? 1 :
103
- (leadByte & 224) === 192 ? 2 :
104
- (leadByte & 240) === 224 ? 3 :
105
- (leadByte & 248) === 240 ? 4 :
106
- 0;
107
- const actualLen = cont + 1;
108
- if (expectedLen === 0 || expectedLen !== actualLen) {
109
- return bytes.subarray(0, leadByteIndex);
110
- }
111
- return bytes;
112
- }
113
- function applyPromptBudget(combinedPrompt) {
114
- const encoder = new TextEncoder();
115
- const promptBytes = encoder.encode(combinedPrompt);
116
- if (promptBytes.length <= constants_js_1.RECOVERY_BUDGET_BYTES) {
117
- return combinedPrompt;
118
- }
119
- const markerText = constants_js_1.TRUNCATION_MARKER;
120
- const omissionNote = `\nOmitted recovery content due to budget constraints.`;
121
- const suffixBytes = encoder.encode(markerText + omissionNote);
122
- const maxContentBytes = constants_js_1.RECOVERY_BUDGET_BYTES - suffixBytes.length;
123
- const truncatedBytes = trimToUtf8Boundary(promptBytes.subarray(0, maxContentBytes));
124
- const decoder = new TextDecoder('utf-8');
125
- return decoder.decode(truncatedBytes) + markerText + omissionNote;
126
- }
127
88
  function resolveParentLoopStep(workflow, stepId) {
128
89
  for (const step of workflow.definition.steps) {
129
90
  if ((0, workflow_js_1.isLoopStepDefinition)(step) && Array.isArray(step.body)) {
@@ -220,6 +181,18 @@ function formatOutputContractRequirements(outputContract) {
220
181
  ];
221
182
  }
222
183
  }
184
+ function formatAssessmentRequirements(assessments) {
185
+ if (assessments.length === 0)
186
+ return [];
187
+ const requirements = [];
188
+ for (const assessment of assessments) {
189
+ requirements.push('Provide an artifact with kind: "wr.assessment"');
190
+ requirements.push(`Assessment target: "${assessment.id}"`);
191
+ requirements.push(`Dimensions: ${assessment.dimensions.map((dimension) => `${dimension.id} (${dimension.levels.join(' | ')})`).join(', ')}`);
192
+ requirements.push('Use only canonical dimension levels. If the engine rejects the artifact, correct the submitted levels instead of inventing new ones.');
193
+ }
194
+ return requirements;
195
+ }
223
196
  function assembleFragmentedPrompt(fragments, context) {
224
197
  return fragments
225
198
  .filter(f => (0, condition_evaluator_js_1.evaluateCondition)(f.when, context))
@@ -256,6 +229,12 @@ function renderPendingPrompt(args) {
256
229
  const outputContract = 'outputContract' in step
257
230
  ? step.outputContract
258
231
  : undefined;
232
+ const stepAssessmentRefs = 'assessmentRefs' in step
233
+ ? step.assessmentRefs
234
+ : undefined;
235
+ const stepAssessments = stepAssessmentRefs && stepAssessmentRefs.length > 0
236
+ ? (args.workflow.definition.assessments ?? []).filter((assessment) => stepAssessmentRefs.includes(assessment.id))
237
+ : [];
259
238
  const isExitStep = outputContract?.contractRef === index_js_2.LOOP_CONTROL_CONTRACT_REF;
260
239
  const loopStep = resolveParentLoopStep(args.workflow, args.stepId);
261
240
  const maxIterations = loopStep?.loop.maxIterations;
@@ -286,6 +265,12 @@ function renderPendingPrompt(args) {
286
265
  ? `\n\n${contractRequirements.map(r => `- ${r}`).join('\n')}`
287
266
  : `\n\n**OUTPUT REQUIREMENTS (System):**\n${contractRequirements.map(r => `- ${r}`).join('\n')}`
288
267
  : '';
268
+ const assessmentRequirements = formatAssessmentRequirements(stepAssessments);
269
+ const assessmentSection = assessmentRequirements.length > 0
270
+ ? cleanResponseFormat
271
+ ? `\n\n${assessmentRequirements.map(r => `- ${r}`).join('\n')}`
272
+ : `\n\n**ASSESSMENT REQUIREMENTS (System):**\n${assessmentRequirements.map(r => `- ${r}`).join('\n')}`
273
+ : '';
289
274
  const isNotesOptional = outputContract !== undefined ||
290
275
  ('notesOptional' in step && step.notesOptional === true);
291
276
  const notesSection = (() => {
@@ -336,7 +321,7 @@ function renderPendingPrompt(args) {
336
321
  const fragmentSuffix = promptFragments && promptFragments.length > 0
337
322
  ? assembleFragmentedPrompt(promptFragments, renderContext)
338
323
  : '';
339
- const enhancedPrompt = loopBanner + basePrompt + requirementsSection + contractSection + notesSection
324
+ const enhancedPrompt = loopBanner + basePrompt + requirementsSection + contractSection + assessmentSection + notesSection
340
325
  + (fragmentSuffix ? '\n\n' + fragmentSuffix : '');
341
326
  if (!args.rehydrateOnly) {
342
327
  return (0, neverthrow_1.ok)({ stepId: args.stepId, title: baseTitle, prompt: enhancedPrompt, agentRole, requireConfirmation });
@@ -352,9 +337,8 @@ function renderPendingPrompt(args) {
352
337
  });
353
338
  }
354
339
  const { run, outputs } = projectionsRes.value;
355
- const sections = buildRecoverySections({
340
+ const segments = buildRecoverySegments({
356
341
  nodeId: args.nodeId,
357
- dag: run,
358
342
  run,
359
343
  outputs,
360
344
  workflow: args.workflow,
@@ -362,12 +346,14 @@ function renderPendingPrompt(args) {
362
346
  loopPath: args.loopPath,
363
347
  functionReferences,
364
348
  });
365
- if (sections.length === 0) {
349
+ if (segments.length === 0) {
366
350
  return (0, neverthrow_1.ok)({ stepId: args.stepId, title: baseTitle, prompt: enhancedPrompt, agentRole, requireConfirmation });
367
351
  }
368
352
  const recoveryHeader = cleanResponseFormat ? 'Your previous work:' : '## Recovery Context';
369
- const recoveryText = `${recoveryHeader}\n\n${sections.join('\n\n')}`;
370
- const combinedPrompt = `${enhancedPrompt}\n\n${recoveryText}`;
371
- const finalPrompt = applyPromptBudget(combinedPrompt);
353
+ const recoveryText = (0, retrieval_contract_js_1.renderBudgetedRehydrateRecovery)({
354
+ header: recoveryHeader,
355
+ segments,
356
+ }).text;
357
+ const finalPrompt = `${enhancedPrompt}\n\n${recoveryText}`;
372
358
  return (0, neverthrow_1.ok)({ stepId: args.stepId, title: baseTitle, prompt: finalPrompt, agentRole, requireConfirmation });
373
359
  }