@exaudeus/workrail 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/application/services/output-normalizer.d.ts +9 -0
- package/dist/application/services/output-normalizer.js +38 -0
- package/dist/application/services/validation-engine.d.ts +15 -1
- package/dist/application/services/validation-engine.js +81 -51
- package/dist/application/services/workflow-compiler.d.ts +3 -0
- package/dist/application/services/workflow-compiler.js +26 -0
- package/dist/application/services/workflow-interpreter.d.ts +4 -1
- package/dist/application/services/workflow-interpreter.js +85 -24
- package/dist/application/services/workflow-service.js +19 -2
- package/dist/manifest.json +379 -75
- package/dist/mcp/handler-factory.d.ts +7 -0
- package/dist/mcp/handler-factory.js +70 -0
- package/dist/mcp/handlers/shared/with-timeout.d.ts +1 -0
- package/dist/mcp/handlers/shared/with-timeout.js +9 -0
- package/dist/mcp/handlers/v2-advance-core.d.ts +45 -0
- package/dist/mcp/handlers/v2-advance-core.js +433 -0
- package/dist/mcp/handlers/v2-context-budget.d.ts +17 -0
- package/dist/mcp/handlers/v2-context-budget.js +169 -0
- package/dist/mcp/handlers/v2-error-mapping.d.ts +34 -0
- package/dist/mcp/handlers/v2-error-mapping.js +125 -0
- package/dist/mcp/handlers/v2-execution-helpers.js +4 -1
- package/dist/mcp/handlers/v2-execution.d.ts +19 -0
- package/dist/mcp/handlers/v2-execution.js +366 -589
- package/dist/mcp/handlers/v2-state-conversion.d.ts +40 -0
- package/dist/mcp/handlers/v2-state-conversion.js +132 -0
- package/dist/mcp/handlers/v2-token-ops.d.ts +33 -0
- package/dist/mcp/handlers/v2-token-ops.js +62 -0
- package/dist/mcp/handlers/v2-workflow.js +3 -8
- package/dist/mcp/handlers/workflow.js +4 -11
- package/dist/mcp/output-schemas.d.ts +514 -38
- package/dist/mcp/output-schemas.js +102 -7
- package/dist/mcp/server.js +23 -127
- package/dist/mcp/tool-descriptions.js +142 -18
- package/dist/mcp/types/workflow-tool-edition.d.ts +28 -0
- package/dist/mcp/types/workflow-tool-edition.js +10 -0
- package/dist/mcp/types.d.ts +2 -0
- package/dist/mcp/v1/tool-registry.d.ts +8 -0
- package/dist/mcp/v1/tool-registry.js +49 -0
- package/dist/mcp/v2/tool-registry.d.ts +2 -5
- package/dist/mcp/v2/tool-registry.js +33 -32
- package/dist/mcp/v2/tools.d.ts +23 -2
- package/dist/mcp/v2/tools.js +38 -7
- package/dist/mcp/workflow-tool-edition-selector.d.ts +4 -0
- package/dist/mcp/workflow-tool-edition-selector.js +13 -0
- package/dist/types/workflow-definition.d.ts +19 -0
- package/dist/v2/durable-core/constants.d.ts +3 -0
- package/dist/v2/durable-core/constants.js +4 -1
- package/dist/v2/durable-core/domain/ack-advance-append-plan.d.ts +15 -7
- package/dist/v2/durable-core/domain/ack-advance-append-plan.js +89 -24
- package/dist/v2/durable-core/domain/artifact-contract-validator.d.ts +31 -0
- package/dist/v2/durable-core/domain/artifact-contract-validator.js +98 -0
- package/dist/v2/durable-core/domain/blocked-node-builder.d.ts +20 -0
- package/dist/v2/durable-core/domain/blocked-node-builder.js +94 -0
- package/dist/v2/durable-core/domain/blocking-decision.d.ts +32 -0
- package/dist/v2/durable-core/domain/blocking-decision.js +41 -0
- package/dist/v2/durable-core/domain/context-merge.d.ts +8 -0
- package/dist/v2/durable-core/domain/context-merge.js +40 -0
- package/dist/v2/durable-core/domain/decision-trace-builder.d.ts +33 -0
- package/dist/v2/durable-core/domain/decision-trace-builder.js +92 -0
- package/dist/v2/durable-core/domain/function-definition-expander.d.ts +14 -0
- package/dist/v2/durable-core/domain/function-definition-expander.js +66 -0
- package/dist/v2/durable-core/domain/gap-builder.d.ts +19 -0
- package/dist/v2/durable-core/domain/gap-builder.js +24 -0
- package/dist/v2/durable-core/domain/loop-control-evaluator.d.ts +13 -0
- package/dist/v2/durable-core/domain/loop-control-evaluator.js +24 -0
- package/dist/v2/durable-core/domain/observation-builder.d.ts +16 -0
- package/dist/v2/durable-core/domain/observation-builder.js +42 -0
- package/dist/v2/durable-core/domain/outputs.js +2 -2
- package/dist/v2/durable-core/domain/prompt-renderer.d.ts +24 -0
- package/dist/v2/durable-core/domain/prompt-renderer.js +200 -0
- package/dist/v2/durable-core/domain/reason-model.d.ts +96 -0
- package/dist/v2/durable-core/domain/reason-model.js +241 -0
- package/dist/v2/durable-core/domain/recap-recovery.d.ts +24 -0
- package/dist/v2/durable-core/domain/recap-recovery.js +71 -0
- package/dist/v2/durable-core/domain/recommendation-warnings.d.ts +20 -0
- package/dist/v2/durable-core/domain/recommendation-warnings.js +35 -0
- package/dist/v2/durable-core/domain/risk-policy-guardrails.d.ts +15 -0
- package/dist/v2/durable-core/domain/risk-policy-guardrails.js +78 -0
- package/dist/v2/durable-core/domain/validation-criteria-validator.d.ts +16 -0
- package/dist/v2/durable-core/domain/validation-criteria-validator.js +46 -0
- package/dist/v2/durable-core/domain/validation-event-builder.d.ts +26 -0
- package/dist/v2/durable-core/domain/validation-event-builder.js +100 -0
- package/dist/v2/durable-core/domain/validation-loader.d.ts +11 -0
- package/dist/v2/durable-core/domain/validation-loader.js +21 -0
- package/dist/v2/durable-core/domain/validation-requirements-extractor.d.ts +2 -0
- package/dist/v2/durable-core/domain/validation-requirements-extractor.js +58 -0
- package/dist/v2/durable-core/projections/snapshot-state.js +1 -1
- package/dist/v2/durable-core/schemas/artifacts/index.d.ts +4 -0
- package/dist/v2/durable-core/schemas/artifacts/index.js +18 -0
- package/dist/v2/durable-core/schemas/artifacts/loop-control.d.ts +66 -0
- package/dist/v2/durable-core/schemas/artifacts/loop-control.js +47 -0
- package/dist/v2/durable-core/schemas/execution-snapshot/blocked-snapshot.d.ts +598 -0
- package/dist/v2/durable-core/schemas/execution-snapshot/blocked-snapshot.js +89 -0
- package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.d.ts +3801 -57
- package/dist/v2/durable-core/schemas/execution-snapshot/execution-snapshot.v1.js +12 -2
- package/dist/v2/durable-core/schemas/execution-snapshot/index.d.ts +2 -0
- package/dist/v2/durable-core/schemas/execution-snapshot/index.js +3 -1
- package/dist/v2/durable-core/schemas/export-bundle/index.d.ts +6201 -2071
- package/dist/v2/durable-core/schemas/session/events.d.ts +216 -45
- package/dist/v2/durable-core/schemas/session/events.js +17 -1
- package/dist/v2/durable-core/schemas/session/validation-event.d.ts +68 -0
- package/dist/v2/durable-core/schemas/session/validation-event.js +52 -0
- package/dist/v2/durable-core/tokens/payloads.d.ts +16 -16
- package/dist/v2/infra/local/workspace-anchor/index.d.ts +9 -0
- package/dist/v2/infra/local/workspace-anchor/index.js +44 -0
- package/dist/v2/ports/workspace-anchor.port.d.ts +18 -0
- package/dist/v2/ports/workspace-anchor.port.js +2 -0
- package/dist/v2/projections/artifacts.d.ts +22 -0
- package/dist/v2/projections/artifacts.js +53 -0
- package/dist/v2/projections/projection-timing.d.ts +13 -0
- package/dist/v2/projections/projection-timing.js +23 -0
- package/dist/v2/projections/run-context.d.ts +22 -0
- package/dist/v2/projections/run-context.js +33 -0
- package/dist/v2/projections/run-dag.d.ts +1 -1
- package/dist/v2/projections/run-status-signals.js +3 -8
- package/package.json +1 -1
- package/spec/workflow.schema.json +60 -0
- package/spec/workflow.schema.v0.0.1.json +38 -0
- package/workflows/coding-task-workflow-agentic.json +11 -18
- package/workflows/test-artifact-loop-control.json +59 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type Result } from 'neverthrow';
|
|
2
|
+
import type { Sha256PortV2 } from '../../ports/sha256.port.js';
|
|
3
|
+
import type { AttemptId } from '../ids/index.js';
|
|
4
|
+
import type { ExecutionSnapshotFileV1 } from '../schemas/execution-snapshot/index.js';
|
|
5
|
+
import type { BlockerReportV1, ReasonV1 } from './reason-model.js';
|
|
6
|
+
export type BlockedNodeBuildError = {
|
|
7
|
+
readonly code: 'BLOCKED_NODE_INVARIANT_VIOLATION';
|
|
8
|
+
readonly message: string;
|
|
9
|
+
} | {
|
|
10
|
+
readonly code: 'BLOCKED_NODE_UNSUPPORTED_STATE';
|
|
11
|
+
readonly message: string;
|
|
12
|
+
};
|
|
13
|
+
export declare function buildBlockedNodeSnapshot(args: {
|
|
14
|
+
readonly priorSnapshot: ExecutionSnapshotFileV1;
|
|
15
|
+
readonly primaryReason: ReasonV1;
|
|
16
|
+
readonly attemptId: AttemptId;
|
|
17
|
+
readonly validationRef: string;
|
|
18
|
+
readonly blockers: BlockerReportV1;
|
|
19
|
+
readonly sha256: Sha256PortV2;
|
|
20
|
+
}): Result<ExecutionSnapshotFileV1, BlockedNodeBuildError>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildBlockedNodeSnapshot = buildBlockedNodeSnapshot;
|
|
4
|
+
const neverthrow_1 = require("neverthrow");
|
|
5
|
+
const attempt_id_derivation_js_1 = require("../ids/attempt-id-derivation.js");
|
|
6
|
+
function toContractViolationReason(reason) {
|
|
7
|
+
switch (reason.kind) {
|
|
8
|
+
case 'invalid_required_output':
|
|
9
|
+
return { kind: 'invalid_required_output', contractRef: reason.contractRef };
|
|
10
|
+
case 'missing_required_output':
|
|
11
|
+
return { kind: 'missing_required_output', contractRef: reason.contractRef };
|
|
12
|
+
case 'missing_context_key':
|
|
13
|
+
return { kind: 'missing_context_key', key: reason.key };
|
|
14
|
+
case 'context_budget_exceeded':
|
|
15
|
+
return { kind: 'context_budget_exceeded' };
|
|
16
|
+
default:
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function toTerminalReason(reason) {
|
|
21
|
+
switch (reason.kind) {
|
|
22
|
+
case 'user_only_dependency':
|
|
23
|
+
return { kind: 'user_only_dependency', detail: reason.detail, stepId: reason.stepId };
|
|
24
|
+
case 'required_capability_unknown':
|
|
25
|
+
return { kind: 'required_capability_unknown', capability: reason.capability };
|
|
26
|
+
case 'required_capability_unavailable':
|
|
27
|
+
return { kind: 'required_capability_unavailable', capability: reason.capability };
|
|
28
|
+
case 'invariant_violation':
|
|
29
|
+
return { kind: 'invariant_violation' };
|
|
30
|
+
case 'storage_corruption_detected':
|
|
31
|
+
return { kind: 'storage_corruption_detected' };
|
|
32
|
+
case 'evaluation_error':
|
|
33
|
+
return { kind: 'evaluation_error' };
|
|
34
|
+
default:
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function buildBlockedPayload(args) {
|
|
39
|
+
const retryable = toContractViolationReason(args.primaryReason);
|
|
40
|
+
if (retryable) {
|
|
41
|
+
const retryAttemptId = (0, attempt_id_derivation_js_1.deriveChildAttemptId)(args.attemptId, args.sha256);
|
|
42
|
+
return (0, neverthrow_1.ok)({
|
|
43
|
+
kind: 'retryable_block',
|
|
44
|
+
reason: retryable,
|
|
45
|
+
retryAttemptId: String(retryAttemptId),
|
|
46
|
+
validationRef: args.validationRef,
|
|
47
|
+
blockers: args.blockers,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
const terminal = toTerminalReason(args.primaryReason);
|
|
51
|
+
if (terminal) {
|
|
52
|
+
return (0, neverthrow_1.ok)({
|
|
53
|
+
kind: 'terminal_block',
|
|
54
|
+
reason: terminal,
|
|
55
|
+
validationRef: args.validationRef,
|
|
56
|
+
blockers: args.blockers,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return (0, neverthrow_1.err)({
|
|
60
|
+
code: 'BLOCKED_NODE_INVARIANT_VIOLATION',
|
|
61
|
+
message: `Unsupported primary reason for blocked snapshot: ${args.primaryReason.kind}`,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function buildBlockedNodeSnapshot(args) {
|
|
65
|
+
const state = args.priorSnapshot.enginePayload.engineState;
|
|
66
|
+
if (state.kind !== 'running' && state.kind !== 'blocked') {
|
|
67
|
+
return (0, neverthrow_1.err)({
|
|
68
|
+
code: 'BLOCKED_NODE_UNSUPPORTED_STATE',
|
|
69
|
+
message: `Blocked nodes can only be created from running or blocked state (got: ${state.kind})`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
const blockedRes = buildBlockedPayload({
|
|
73
|
+
primaryReason: args.primaryReason,
|
|
74
|
+
attemptId: args.attemptId,
|
|
75
|
+
validationRef: args.validationRef,
|
|
76
|
+
blockers: args.blockers,
|
|
77
|
+
sha256: args.sha256,
|
|
78
|
+
});
|
|
79
|
+
if (blockedRes.isErr())
|
|
80
|
+
return (0, neverthrow_1.err)(blockedRes.error);
|
|
81
|
+
return (0, neverthrow_1.ok)({
|
|
82
|
+
...args.priorSnapshot,
|
|
83
|
+
enginePayload: {
|
|
84
|
+
...args.priorSnapshot.enginePayload,
|
|
85
|
+
engineState: {
|
|
86
|
+
kind: 'blocked',
|
|
87
|
+
completed: state.completed,
|
|
88
|
+
loopStack: state.loopStack,
|
|
89
|
+
pending: state.pending,
|
|
90
|
+
blocked: blockedRes.value,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type Result } from 'neverthrow';
|
|
2
|
+
import type { ValidationResult } from '../../../types/validation.js';
|
|
3
|
+
import type { ReasonV1 } from './reason-model.js';
|
|
4
|
+
export type BlockingDecisionError = {
|
|
5
|
+
readonly code: 'INVALID_DELIMITER_SAFE_ID';
|
|
6
|
+
readonly message: string;
|
|
7
|
+
};
|
|
8
|
+
export type OutputRequirementStatus = {
|
|
9
|
+
readonly kind: 'not_required';
|
|
10
|
+
} | {
|
|
11
|
+
readonly kind: 'missing';
|
|
12
|
+
readonly contractRef: string;
|
|
13
|
+
} | {
|
|
14
|
+
readonly kind: 'invalid';
|
|
15
|
+
readonly contractRef: string;
|
|
16
|
+
readonly validation: ValidationResult;
|
|
17
|
+
};
|
|
18
|
+
export type CapabilityRequirementStatus = {
|
|
19
|
+
readonly kind: 'not_required';
|
|
20
|
+
} | {
|
|
21
|
+
readonly kind: 'unknown';
|
|
22
|
+
readonly capability: 'delegation' | 'web_browsing';
|
|
23
|
+
} | {
|
|
24
|
+
readonly kind: 'unavailable';
|
|
25
|
+
readonly capability: 'delegation' | 'web_browsing';
|
|
26
|
+
};
|
|
27
|
+
export declare function detectBlockingReasonsV1(args: {
|
|
28
|
+
readonly missingContextKeys?: readonly string[];
|
|
29
|
+
readonly contextBudgetExceeded?: boolean;
|
|
30
|
+
readonly outputRequirement?: OutputRequirementStatus;
|
|
31
|
+
readonly capabilityRequirement?: CapabilityRequirementStatus;
|
|
32
|
+
}): Result<readonly ReasonV1[], BlockingDecisionError>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.detectBlockingReasonsV1 = detectBlockingReasonsV1;
|
|
4
|
+
const neverthrow_1 = require("neverthrow");
|
|
5
|
+
const constants_js_1 = require("../constants.js");
|
|
6
|
+
function detectBlockingReasonsV1(args) {
|
|
7
|
+
const reasons = [];
|
|
8
|
+
if (args.contextBudgetExceeded) {
|
|
9
|
+
reasons.push({ kind: 'context_budget_exceeded' });
|
|
10
|
+
}
|
|
11
|
+
if (args.missingContextKeys) {
|
|
12
|
+
for (const key of args.missingContextKeys) {
|
|
13
|
+
if (!constants_js_1.DELIMITER_SAFE_ID_PATTERN.test(key)) {
|
|
14
|
+
return (0, neverthrow_1.err)({
|
|
15
|
+
code: 'INVALID_DELIMITER_SAFE_ID',
|
|
16
|
+
message: `context key must be delimiter-safe: [a-z0-9_-]+ (got: ${key})`,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
reasons.push({ kind: 'missing_context_key', key });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const outReq = args.outputRequirement;
|
|
23
|
+
if (outReq && outReq.kind !== 'not_required') {
|
|
24
|
+
if (outReq.kind === 'missing') {
|
|
25
|
+
reasons.push({ kind: 'missing_required_output', contractRef: outReq.contractRef });
|
|
26
|
+
}
|
|
27
|
+
if (outReq.kind === 'invalid') {
|
|
28
|
+
reasons.push({ kind: 'invalid_required_output', contractRef: outReq.contractRef });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const capReq = args.capabilityRequirement;
|
|
32
|
+
if (capReq && capReq.kind !== 'not_required') {
|
|
33
|
+
if (capReq.kind === 'unknown') {
|
|
34
|
+
reasons.push({ kind: 'required_capability_unknown', capability: capReq.capability });
|
|
35
|
+
}
|
|
36
|
+
if (capReq.kind === 'unavailable') {
|
|
37
|
+
reasons.push({ kind: 'required_capability_unavailable', capability: capReq.capability });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return (0, neverthrow_1.ok)(reasons);
|
|
41
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { JsonObject } from '../canonical/json-types.js';
|
|
2
|
+
import type { Result } from 'neverthrow';
|
|
3
|
+
export type ContextMergeError = {
|
|
4
|
+
readonly code: 'RESERVED_KEY_REJECTED';
|
|
5
|
+
readonly message: string;
|
|
6
|
+
readonly key: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function mergeContext(stored: JsonObject | undefined, delta: JsonObject | undefined): Result<JsonObject, ContextMergeError>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mergeContext = mergeContext;
|
|
4
|
+
const neverthrow_1 = require("neverthrow");
|
|
5
|
+
const RESERVED_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
6
|
+
function mergeContext(stored, delta) {
|
|
7
|
+
if (!delta)
|
|
8
|
+
return (0, neverthrow_1.ok)(stored ?? {});
|
|
9
|
+
if (!stored)
|
|
10
|
+
return stripNullAndValidate(delta);
|
|
11
|
+
for (const reservedKey of RESERVED_KEYS) {
|
|
12
|
+
if (reservedKey in delta && Object.prototype.hasOwnProperty.call(delta, reservedKey)) {
|
|
13
|
+
return (0, neverthrow_1.err)({
|
|
14
|
+
code: 'RESERVED_KEY_REJECTED',
|
|
15
|
+
message: `Context key '${reservedKey}' is reserved and cannot be used.`,
|
|
16
|
+
key: reservedKey,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const tombstones = new Set(Object.entries(delta)
|
|
21
|
+
.filter(([_, v]) => v === null)
|
|
22
|
+
.map(([k]) => k));
|
|
23
|
+
const overrides = Object.fromEntries(Object.entries(delta).filter(([_, v]) => v !== null && v !== undefined));
|
|
24
|
+
const mergedEntries = Object.entries(stored)
|
|
25
|
+
.filter(([k]) => !tombstones.has(k))
|
|
26
|
+
.concat(Object.entries(overrides));
|
|
27
|
+
return (0, neverthrow_1.ok)(Object.fromEntries(mergedEntries));
|
|
28
|
+
}
|
|
29
|
+
function stripNullAndValidate(obj) {
|
|
30
|
+
const reservedKey = Object.keys(obj).find(k => RESERVED_KEYS.has(k));
|
|
31
|
+
if (reservedKey) {
|
|
32
|
+
return (0, neverthrow_1.err)({
|
|
33
|
+
code: 'RESERVED_KEY_REJECTED',
|
|
34
|
+
message: `Context key '${reservedKey}' is reserved and cannot be used.`,
|
|
35
|
+
key: reservedKey,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const entries = Object.entries(obj).filter(([_, v]) => v !== null && v !== undefined);
|
|
39
|
+
return (0, neverthrow_1.ok)(Object.fromEntries(entries));
|
|
40
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Result } from 'neverthrow';
|
|
2
|
+
export type DecisionTraceEntryKind = 'selected_next_step' | 'evaluated_condition' | 'entered_loop' | 'exited_loop' | 'detected_non_tip_advance';
|
|
3
|
+
export type DecisionTraceRef = {
|
|
4
|
+
readonly kind: 'step_id';
|
|
5
|
+
readonly stepId: string;
|
|
6
|
+
} | {
|
|
7
|
+
readonly kind: 'loop_id';
|
|
8
|
+
readonly loopId: string;
|
|
9
|
+
} | {
|
|
10
|
+
readonly kind: 'condition_id';
|
|
11
|
+
readonly conditionId: string;
|
|
12
|
+
} | {
|
|
13
|
+
readonly kind: 'iteration';
|
|
14
|
+
readonly value: number;
|
|
15
|
+
};
|
|
16
|
+
export interface DecisionTraceEntry {
|
|
17
|
+
readonly kind: DecisionTraceEntryKind;
|
|
18
|
+
readonly summary: string;
|
|
19
|
+
readonly refs?: readonly DecisionTraceRef[];
|
|
20
|
+
}
|
|
21
|
+
export declare function traceEnteredLoop(loopId: string, iteration: number): DecisionTraceEntry;
|
|
22
|
+
export declare function traceEvaluatedCondition(loopId: string, iteration: number, result: boolean, source: 'artifact' | 'context' | 'legacy'): DecisionTraceEntry;
|
|
23
|
+
export declare function traceExitedLoop(loopId: string, reason: string): DecisionTraceEntry;
|
|
24
|
+
export declare function traceSelectedNextStep(stepId: string): DecisionTraceEntry;
|
|
25
|
+
export declare function applyTraceBudget(entries: readonly DecisionTraceEntry[]): readonly DecisionTraceEntry[];
|
|
26
|
+
export declare function buildDecisionTraceEventData(traceId: string, entries: readonly DecisionTraceEntry[]): Result<{
|
|
27
|
+
readonly traceId: string;
|
|
28
|
+
readonly entries: readonly {
|
|
29
|
+
readonly kind: string;
|
|
30
|
+
readonly summary: string;
|
|
31
|
+
readonly refs?: readonly DecisionTraceRef[];
|
|
32
|
+
}[];
|
|
33
|
+
}, never>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.traceEnteredLoop = traceEnteredLoop;
|
|
4
|
+
exports.traceEvaluatedCondition = traceEvaluatedCondition;
|
|
5
|
+
exports.traceExitedLoop = traceExitedLoop;
|
|
6
|
+
exports.traceSelectedNextStep = traceSelectedNextStep;
|
|
7
|
+
exports.applyTraceBudget = applyTraceBudget;
|
|
8
|
+
exports.buildDecisionTraceEventData = buildDecisionTraceEventData;
|
|
9
|
+
const neverthrow_1 = require("neverthrow");
|
|
10
|
+
const constants_js_1 = require("../constants.js");
|
|
11
|
+
function traceEnteredLoop(loopId, iteration) {
|
|
12
|
+
return {
|
|
13
|
+
kind: 'entered_loop',
|
|
14
|
+
summary: `Entered loop '${loopId}' at iteration ${iteration}.`,
|
|
15
|
+
refs: [{ kind: 'loop_id', loopId }, { kind: 'iteration', value: iteration }],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function traceEvaluatedCondition(loopId, iteration, result, source) {
|
|
19
|
+
const decision = result ? 'continue' : 'exit';
|
|
20
|
+
return {
|
|
21
|
+
kind: 'evaluated_condition',
|
|
22
|
+
summary: `Evaluated ${source} condition for loop '${loopId}' at iteration ${iteration}: ${decision}.`,
|
|
23
|
+
refs: [{ kind: 'loop_id', loopId }, { kind: 'iteration', value: iteration }],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function traceExitedLoop(loopId, reason) {
|
|
27
|
+
return {
|
|
28
|
+
kind: 'exited_loop',
|
|
29
|
+
summary: `Exited loop '${loopId}': ${reason}.`,
|
|
30
|
+
refs: [{ kind: 'loop_id', loopId }],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function traceSelectedNextStep(stepId) {
|
|
34
|
+
return {
|
|
35
|
+
kind: 'selected_next_step',
|
|
36
|
+
summary: `Selected next step '${stepId}'.`,
|
|
37
|
+
refs: [{ kind: 'step_id', stepId }],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const textEncoder = new TextEncoder();
|
|
41
|
+
function utf8ByteLength(s) {
|
|
42
|
+
return textEncoder.encode(s).length;
|
|
43
|
+
}
|
|
44
|
+
function applyTraceBudget(entries) {
|
|
45
|
+
const capped = entries.slice(0, constants_js_1.MAX_DECISION_TRACE_ENTRIES);
|
|
46
|
+
let totalBytes = 0;
|
|
47
|
+
const result = [];
|
|
48
|
+
for (const entry of capped) {
|
|
49
|
+
let summary = entry.summary;
|
|
50
|
+
if (utf8ByteLength(summary) > constants_js_1.MAX_DECISION_TRACE_ENTRY_SUMMARY_BYTES) {
|
|
51
|
+
summary = truncateToUtf8Budget(summary, constants_js_1.MAX_DECISION_TRACE_ENTRY_SUMMARY_BYTES);
|
|
52
|
+
}
|
|
53
|
+
const entryBytes = utf8ByteLength(summary);
|
|
54
|
+
if (totalBytes + entryBytes > constants_js_1.MAX_DECISION_TRACE_TOTAL_BYTES) {
|
|
55
|
+
const remaining = constants_js_1.MAX_DECISION_TRACE_TOTAL_BYTES - totalBytes;
|
|
56
|
+
if (remaining > utf8ByteLength(constants_js_1.TRUNCATION_MARKER) + 10) {
|
|
57
|
+
summary = truncateToUtf8Budget(summary, remaining);
|
|
58
|
+
result.push({ ...entry, summary });
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
totalBytes += entryBytes;
|
|
63
|
+
result.push(summary === entry.summary ? entry : { ...entry, summary });
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
function truncateToUtf8Budget(s, maxBytes) {
|
|
68
|
+
const markerBytes = utf8ByteLength(constants_js_1.TRUNCATION_MARKER);
|
|
69
|
+
const targetBytes = maxBytes - markerBytes;
|
|
70
|
+
if (targetBytes <= 0)
|
|
71
|
+
return constants_js_1.TRUNCATION_MARKER;
|
|
72
|
+
const encoded = textEncoder.encode(s);
|
|
73
|
+
if (encoded.length <= maxBytes)
|
|
74
|
+
return s;
|
|
75
|
+
let end = targetBytes;
|
|
76
|
+
while (end > 0 && (encoded[end] & 0xc0) === 0x80) {
|
|
77
|
+
end--;
|
|
78
|
+
}
|
|
79
|
+
const decoder = new TextDecoder();
|
|
80
|
+
return decoder.decode(encoded.slice(0, end)) + constants_js_1.TRUNCATION_MARKER;
|
|
81
|
+
}
|
|
82
|
+
function buildDecisionTraceEventData(traceId, entries) {
|
|
83
|
+
const budgeted = applyTraceBudget(entries);
|
|
84
|
+
return (0, neverthrow_1.ok)({
|
|
85
|
+
traceId,
|
|
86
|
+
entries: budgeted.map((e) => ({
|
|
87
|
+
kind: e.kind,
|
|
88
|
+
summary: e.summary,
|
|
89
|
+
refs: e.refs && e.refs.length > 0 ? e.refs : undefined,
|
|
90
|
+
})),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Result } from 'neverthrow';
|
|
2
|
+
import type { Workflow, FunctionDefinition } from '../../../types/workflow.js';
|
|
3
|
+
import type { LoopPathFrameV1 } from '../schemas/execution-snapshot/index.js';
|
|
4
|
+
export type FunctionExpansionError = {
|
|
5
|
+
readonly code: 'FUNCTION_EXPANSION_FAILED';
|
|
6
|
+
readonly message: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function expandFunctionDefinitions(args: {
|
|
9
|
+
readonly workflow: Workflow;
|
|
10
|
+
readonly stepId: string;
|
|
11
|
+
readonly loopPath: readonly LoopPathFrameV1[];
|
|
12
|
+
readonly functionReferences: readonly string[];
|
|
13
|
+
}): Result<readonly FunctionDefinition[], FunctionExpansionError>;
|
|
14
|
+
export declare function formatFunctionDef(def: FunctionDefinition): string;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.expandFunctionDefinitions = expandFunctionDefinitions;
|
|
4
|
+
exports.formatFunctionDef = formatFunctionDef;
|
|
5
|
+
const neverthrow_1 = require("neverthrow");
|
|
6
|
+
const workflow_js_1 = require("../../../types/workflow.js");
|
|
7
|
+
function findLoopById(workflow, loopId) {
|
|
8
|
+
function searchSteps(steps) {
|
|
9
|
+
for (const step of steps) {
|
|
10
|
+
if (!(0, workflow_js_1.isLoopStepDefinition)(step))
|
|
11
|
+
continue;
|
|
12
|
+
if (step.id === loopId)
|
|
13
|
+
return step;
|
|
14
|
+
if (Array.isArray(step.body)) {
|
|
15
|
+
const found = searchSteps(step.body);
|
|
16
|
+
if (found)
|
|
17
|
+
return found;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return searchSteps(workflow.definition.steps);
|
|
23
|
+
}
|
|
24
|
+
function getWorkflowScopeDefs(workflow) {
|
|
25
|
+
return workflow.definition.functionDefinitions?.filter(f => !f.scope || f.scope === 'workflow') ?? [];
|
|
26
|
+
}
|
|
27
|
+
function getLoopScopeDefs(args) {
|
|
28
|
+
return args.loopPath.flatMap(frame => {
|
|
29
|
+
const loopStep = findLoopById(args.workflow, String(frame.loopId));
|
|
30
|
+
return loopStep?.functionDefinitions?.filter(f => !f.scope || f.scope === 'loop') ?? [];
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function getStepScopeDefs(args) {
|
|
34
|
+
const step = args.workflow.definition.steps.find(s => s.id === args.stepId);
|
|
35
|
+
return step?.functionDefinitions?.filter(f => !f.scope || f.scope === 'step') ?? [];
|
|
36
|
+
}
|
|
37
|
+
function expandFunctionDefinitions(args) {
|
|
38
|
+
const allDefs = [
|
|
39
|
+
...getWorkflowScopeDefs(args.workflow),
|
|
40
|
+
...getLoopScopeDefs({ workflow: args.workflow, loopPath: args.loopPath }),
|
|
41
|
+
...getStepScopeDefs({ workflow: args.workflow, stepId: args.stepId }),
|
|
42
|
+
];
|
|
43
|
+
const deduped = new Map();
|
|
44
|
+
for (const def of allDefs) {
|
|
45
|
+
if (!deduped.has(def.name)) {
|
|
46
|
+
deduped.set(def.name, def);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const filtered = args.functionReferences.length > 0
|
|
50
|
+
? Array.from(deduped.values()).filter(f => args.functionReferences.includes(f.name))
|
|
51
|
+
: Array.from(deduped.values());
|
|
52
|
+
const scopePriority = { step: 0, loop: 1, workflow: 2 };
|
|
53
|
+
const sorted = filtered.sort((a, b) => {
|
|
54
|
+
const aScope = a.scope ?? 'workflow';
|
|
55
|
+
const bScope = b.scope ?? 'workflow';
|
|
56
|
+
const aPri = scopePriority[aScope] ?? 2;
|
|
57
|
+
const bPri = scopePriority[bScope] ?? 2;
|
|
58
|
+
if (aPri !== bPri)
|
|
59
|
+
return aPri - bPri;
|
|
60
|
+
return a.name.localeCompare(b.name, 'en-US');
|
|
61
|
+
});
|
|
62
|
+
return (0, neverthrow_1.ok)(sorted);
|
|
63
|
+
}
|
|
64
|
+
function formatFunctionDef(def) {
|
|
65
|
+
return `function ${def.name}(...)\n ${def.definition}`;
|
|
66
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { DomainEventV1 } from '../schemas/session/index.js';
|
|
2
|
+
import type { ReasonV1 } from './reason-model.js';
|
|
3
|
+
export type GapEvidenceRefV1 = {
|
|
4
|
+
readonly kind: 'event';
|
|
5
|
+
readonly eventId: string;
|
|
6
|
+
} | {
|
|
7
|
+
readonly kind: 'output';
|
|
8
|
+
readonly outputId: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function buildGapRecordedEventV1(args: {
|
|
11
|
+
readonly eventId: string;
|
|
12
|
+
readonly eventIndex: number;
|
|
13
|
+
readonly sessionId: string;
|
|
14
|
+
readonly runId: string;
|
|
15
|
+
readonly nodeId: string;
|
|
16
|
+
readonly gapId: string;
|
|
17
|
+
readonly reason: ReasonV1;
|
|
18
|
+
readonly evidenceRefs?: readonly GapEvidenceRefV1[];
|
|
19
|
+
}): DomainEventV1;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildGapRecordedEventV1 = buildGapRecordedEventV1;
|
|
4
|
+
const reason_model_js_1 = require("./reason-model.js");
|
|
5
|
+
function buildGapRecordedEventV1(args) {
|
|
6
|
+
const { severity, reason, summary } = (0, reason_model_js_1.reasonToGap)(args.reason);
|
|
7
|
+
return {
|
|
8
|
+
v: 1,
|
|
9
|
+
eventId: args.eventId,
|
|
10
|
+
eventIndex: args.eventIndex,
|
|
11
|
+
sessionId: args.sessionId,
|
|
12
|
+
kind: 'gap_recorded',
|
|
13
|
+
dedupeKey: `gap_recorded:${args.sessionId}:${args.gapId}`,
|
|
14
|
+
scope: { runId: args.runId, nodeId: args.nodeId },
|
|
15
|
+
data: {
|
|
16
|
+
gapId: args.gapId,
|
|
17
|
+
severity,
|
|
18
|
+
reason,
|
|
19
|
+
summary,
|
|
20
|
+
resolution: { kind: 'unresolved' },
|
|
21
|
+
evidenceRefs: args.evidenceRefs,
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type LoopControlDecision, type LoopControlArtifactV1 } from '../schemas/artifacts/index.js';
|
|
2
|
+
export type LoopControlEvaluationResult = {
|
|
3
|
+
readonly kind: 'found';
|
|
4
|
+
readonly decision: LoopControlDecision;
|
|
5
|
+
readonly artifact: LoopControlArtifactV1;
|
|
6
|
+
} | {
|
|
7
|
+
readonly kind: 'not_found';
|
|
8
|
+
readonly reason: string;
|
|
9
|
+
} | {
|
|
10
|
+
readonly kind: 'invalid';
|
|
11
|
+
readonly reason: string;
|
|
12
|
+
};
|
|
13
|
+
export declare function evaluateLoopControlFromArtifacts(artifacts: readonly unknown[], loopId: string): LoopControlEvaluationResult;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.evaluateLoopControlFromArtifacts = evaluateLoopControlFromArtifacts;
|
|
4
|
+
const index_js_1 = require("../schemas/artifacts/index.js");
|
|
5
|
+
function evaluateLoopControlFromArtifacts(artifacts, loopId) {
|
|
6
|
+
if (artifacts.length === 0) {
|
|
7
|
+
return {
|
|
8
|
+
kind: 'not_found',
|
|
9
|
+
reason: `No artifacts provided to evaluate loop control for loopId=${loopId}`,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
const artifact = (0, index_js_1.findLoopControlArtifact)(artifacts, loopId);
|
|
13
|
+
if (!artifact) {
|
|
14
|
+
return {
|
|
15
|
+
kind: 'not_found',
|
|
16
|
+
reason: `No loop control artifact found for loopId=${loopId}`,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
kind: 'found',
|
|
21
|
+
decision: artifact.decision,
|
|
22
|
+
artifact,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { WorkspaceAnchor } from '../../ports/workspace-anchor.port.js';
|
|
2
|
+
export interface ObservationEventData {
|
|
3
|
+
readonly key: 'git_branch' | 'git_head_sha' | 'repo_root_hash';
|
|
4
|
+
readonly value: {
|
|
5
|
+
readonly type: 'short_string';
|
|
6
|
+
readonly value: string;
|
|
7
|
+
} | {
|
|
8
|
+
readonly type: 'git_sha1';
|
|
9
|
+
readonly value: string;
|
|
10
|
+
} | {
|
|
11
|
+
readonly type: 'sha256';
|
|
12
|
+
readonly value: string;
|
|
13
|
+
};
|
|
14
|
+
readonly confidence: 'low' | 'med' | 'high';
|
|
15
|
+
}
|
|
16
|
+
export declare function anchorsToObservations(anchors: readonly WorkspaceAnchor[]): readonly ObservationEventData[];
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.anchorsToObservations = anchorsToObservations;
|
|
4
|
+
function anchorsToObservations(anchors) {
|
|
5
|
+
const observations = [];
|
|
6
|
+
for (const anchor of anchors) {
|
|
7
|
+
switch (anchor.key) {
|
|
8
|
+
case 'git_branch':
|
|
9
|
+
if (anchor.value.length > 80)
|
|
10
|
+
break;
|
|
11
|
+
observations.push({
|
|
12
|
+
key: 'git_branch',
|
|
13
|
+
value: { type: 'short_string', value: anchor.value },
|
|
14
|
+
confidence: 'high',
|
|
15
|
+
});
|
|
16
|
+
break;
|
|
17
|
+
case 'git_head_sha':
|
|
18
|
+
if (!/^[0-9a-f]{40}$/.test(anchor.value))
|
|
19
|
+
break;
|
|
20
|
+
observations.push({
|
|
21
|
+
key: 'git_head_sha',
|
|
22
|
+
value: { type: 'git_sha1', value: anchor.value },
|
|
23
|
+
confidence: 'high',
|
|
24
|
+
});
|
|
25
|
+
break;
|
|
26
|
+
case 'repo_root_hash':
|
|
27
|
+
if (!/^sha256:[0-9a-f]{64}$/.test(anchor.value))
|
|
28
|
+
break;
|
|
29
|
+
observations.push({
|
|
30
|
+
key: 'repo_root_hash',
|
|
31
|
+
value: { type: 'sha256', value: anchor.value },
|
|
32
|
+
confidence: 'high',
|
|
33
|
+
});
|
|
34
|
+
break;
|
|
35
|
+
default: {
|
|
36
|
+
const _exhaustive = anchor;
|
|
37
|
+
return _exhaustive;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return observations;
|
|
42
|
+
}
|
|
@@ -9,10 +9,10 @@ function normalizeOutputsForAppend(outputs) {
|
|
|
9
9
|
const aSha = a.payload.sha256 ?? '';
|
|
10
10
|
const bSha = b.payload.sha256 ?? '';
|
|
11
11
|
if (aSha !== bSha)
|
|
12
|
-
return aSha.localeCompare(bSha);
|
|
12
|
+
return aSha.localeCompare(bSha, 'en-US');
|
|
13
13
|
const aType = a.payload.contentType ?? '';
|
|
14
14
|
const bType = b.payload.contentType ?? '';
|
|
15
|
-
return aType.localeCompare(bType);
|
|
15
|
+
return aType.localeCompare(bType, 'en-US');
|
|
16
16
|
});
|
|
17
17
|
return [...recapFirst, ...sortedArtifacts];
|
|
18
18
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Result } from 'neverthrow';
|
|
2
|
+
import type { Workflow } from '../../../types/workflow.js';
|
|
3
|
+
import type { LoadedSessionTruthV2 } from '../../ports/session-event-log-store.port.js';
|
|
4
|
+
import type { LoopPathFrameV1 } from '../schemas/execution-snapshot/index.js';
|
|
5
|
+
import type { NodeId, RunId } from '../ids/index.js';
|
|
6
|
+
export type PromptRenderError = {
|
|
7
|
+
readonly code: 'RENDER_FAILED';
|
|
8
|
+
readonly message: string;
|
|
9
|
+
};
|
|
10
|
+
export interface StepMetadata {
|
|
11
|
+
readonly stepId: string;
|
|
12
|
+
readonly title: string;
|
|
13
|
+
readonly prompt: string;
|
|
14
|
+
readonly requireConfirmation: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function renderPendingPrompt(args: {
|
|
17
|
+
readonly workflow: Workflow;
|
|
18
|
+
readonly stepId: string;
|
|
19
|
+
readonly loopPath: readonly LoopPathFrameV1[];
|
|
20
|
+
readonly truth: LoadedSessionTruthV2;
|
|
21
|
+
readonly runId: RunId;
|
|
22
|
+
readonly nodeId: NodeId;
|
|
23
|
+
readonly rehydrateOnly: boolean;
|
|
24
|
+
}): Result<StepMetadata, PromptRenderError>;
|