@exaudeus/workrail 3.15.0 → 3.16.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/workflow-service.d.ts +2 -0
- package/dist/application/services/workflow-service.js +3 -0
- package/dist/console/assets/index-BE5PAgPO.js +28 -0
- package/dist/console/assets/index-BZNM03t1.css +1 -0
- package/dist/console/index.html +2 -2
- package/dist/env-flags.d.ts +1 -0
- package/dist/env-flags.js +4 -0
- package/dist/infrastructure/session/HttpServer.d.ts +3 -3
- package/dist/infrastructure/session/HttpServer.js +68 -74
- package/dist/infrastructure/storage/caching-workflow-storage.d.ts +2 -0
- package/dist/infrastructure/storage/caching-workflow-storage.js +15 -6
- package/dist/infrastructure/storage/file-workflow-storage.js +3 -4
- package/dist/infrastructure/storage/schema-validating-workflow-storage.js +9 -8
- package/dist/manifest.json +257 -193
- package/dist/mcp/assert-output.d.ts +37 -0
- package/dist/mcp/assert-output.js +52 -0
- package/dist/mcp/boundary-coercion.d.ts +1 -0
- package/dist/mcp/boundary-coercion.js +44 -0
- package/dist/mcp/dev-mode.d.ts +1 -0
- package/dist/mcp/dev-mode.js +4 -0
- package/dist/mcp/handler-factory.js +12 -9
- package/dist/mcp/handlers/session.js +8 -9
- package/dist/mcp/handlers/v2-advance-core/event-builders.d.ts +2 -0
- package/dist/mcp/handlers/v2-advance-core/event-builders.js +6 -6
- package/dist/mcp/handlers/v2-advance-core/index.d.ts +2 -0
- package/dist/mcp/handlers/v2-advance-core/index.js +4 -3
- package/dist/mcp/handlers/v2-advance-core/input-validation.d.ts +2 -0
- package/dist/mcp/handlers/v2-advance-core/input-validation.js +32 -9
- package/dist/mcp/handlers/v2-advance-core/outcome-blocked.d.ts +2 -0
- package/dist/mcp/handlers/v2-advance-core/outcome-blocked.js +1 -1
- package/dist/mcp/handlers/v2-advance-core/outcome-success.d.ts +2 -0
- package/dist/mcp/handlers/v2-advance-core/outcome-success.js +1 -1
- package/dist/mcp/handlers/v2-checkpoint.d.ts +1 -1
- package/dist/mcp/handlers/v2-checkpoint.js +5 -6
- package/dist/mcp/handlers/v2-execution/advance.d.ts +4 -2
- package/dist/mcp/handlers/v2-execution/advance.js +5 -7
- package/dist/mcp/handlers/v2-execution/continue-advance.js +56 -26
- package/dist/mcp/handlers/v2-execution/continue-rehydrate.d.ts +1 -1
- package/dist/mcp/handlers/v2-execution/continue-rehydrate.js +9 -9
- package/dist/mcp/handlers/v2-execution/replay.d.ts +6 -4
- package/dist/mcp/handlers/v2-execution/replay.js +47 -30
- package/dist/mcp/handlers/v2-execution/start.d.ts +2 -3
- package/dist/mcp/handlers/v2-execution/start.js +11 -11
- package/dist/mcp/handlers/v2-execution/workflow-object-cache.d.ts +5 -0
- package/dist/mcp/handlers/v2-execution/workflow-object-cache.js +19 -0
- package/dist/mcp/handlers/v2-execution-helpers.d.ts +1 -0
- package/dist/mcp/handlers/v2-execution-helpers.js +23 -7
- package/dist/mcp/handlers/v2-resume.d.ts +1 -1
- package/dist/mcp/handlers/v2-resume.js +3 -4
- package/dist/mcp/handlers/v2-state-conversion.js +5 -1
- package/dist/mcp/handlers/v2-workflow.d.ts +80 -0
- package/dist/mcp/handlers/v2-workflow.js +36 -21
- package/dist/mcp/handlers/workflow.d.ts +2 -5
- package/dist/mcp/handlers/workflow.js +15 -12
- package/dist/mcp/output-schemas.d.ts +20 -27
- package/dist/mcp/output-schemas.js +5 -7
- package/dist/mcp/server.js +22 -4
- package/dist/mcp/tool-call-timing.d.ts +24 -0
- package/dist/mcp/tool-call-timing.js +85 -0
- package/dist/mcp/transports/http-entry.js +3 -2
- package/dist/mcp/transports/http-listener.d.ts +1 -0
- package/dist/mcp/transports/http-listener.js +25 -0
- package/dist/mcp/transports/shutdown-hooks.d.ts +4 -1
- package/dist/mcp/transports/shutdown-hooks.js +3 -2
- package/dist/mcp/transports/stdio-entry.js +6 -28
- package/dist/mcp/v2-response-formatter.js +2 -4
- package/dist/mcp/validation/schema-introspection.d.ts +1 -0
- package/dist/mcp/validation/schema-introspection.js +15 -5
- package/dist/mcp/validation/suggestion-generator.js +2 -2
- package/dist/runtime/adapters/node-process-signals.d.ts +1 -0
- package/dist/runtime/adapters/node-process-signals.js +5 -0
- package/dist/runtime/adapters/noop-process-signals.d.ts +1 -0
- package/dist/runtime/adapters/noop-process-signals.js +2 -0
- package/dist/runtime/ports/process-signals.d.ts +1 -0
- package/dist/types/workflow-definition.d.ts +2 -0
- package/dist/types/workflow.d.ts +3 -0
- package/dist/types/workflow.js +35 -26
- package/dist/v2/durable-core/domain/context-template-resolver.js +2 -2
- package/dist/v2/durable-core/domain/function-definition-expander.js +2 -17
- package/dist/v2/durable-core/domain/prompt-renderer.d.ts +1 -0
- package/dist/v2/durable-core/domain/prompt-renderer.js +23 -18
- package/dist/v2/durable-core/domain/recap-recovery.js +23 -16
- package/dist/v2/durable-core/domain/retrieval-contract.js +13 -7
- package/dist/v2/durable-core/session-index.d.ts +22 -0
- package/dist/v2/durable-core/session-index.js +58 -0
- package/dist/v2/durable-core/sorted-event-log.d.ts +6 -0
- package/dist/v2/durable-core/sorted-event-log.js +15 -0
- package/dist/v2/infra/local/fs/index.js +8 -8
- package/dist/v2/infra/local/session-store/index.d.ts +1 -1
- package/dist/v2/infra/local/session-store/index.js +71 -61
- package/dist/v2/infra/local/session-summary-provider/index.js +9 -4
- package/dist/v2/infra/local/snapshot-store/index.js +2 -1
- package/dist/v2/ports/session-event-log-store.port.d.ts +1 -1
- package/dist/v2/projections/assessment-consequences.d.ts +2 -1
- package/dist/v2/projections/assessment-consequences.js +0 -5
- package/dist/v2/projections/assessments.d.ts +2 -1
- package/dist/v2/projections/assessments.js +2 -4
- package/dist/v2/projections/gaps.d.ts +2 -1
- package/dist/v2/projections/gaps.js +0 -5
- package/dist/v2/projections/preferences.d.ts +2 -1
- package/dist/v2/projections/preferences.js +0 -5
- package/dist/v2/projections/run-context.d.ts +2 -2
- package/dist/v2/projections/run-context.js +0 -5
- package/dist/v2/projections/run-dag.js +7 -1
- package/dist/v2/projections/run-execution-trace.d.ts +8 -0
- package/dist/v2/projections/run-execution-trace.js +124 -0
- package/dist/v2/projections/run-status-signals.d.ts +2 -2
- package/dist/v2/usecases/console-routes.d.ts +3 -1
- package/dist/v2/usecases/console-routes.js +123 -25
- package/dist/v2/usecases/console-service.d.ts +1 -0
- package/dist/v2/usecases/console-service.js +83 -25
- package/dist/v2/usecases/console-types.d.ts +53 -0
- package/dist/v2/usecases/worktree-service.js +32 -1
- package/package.json +6 -5
- package/spec/workflow.schema.json +18 -0
- package/workflows/adaptive-ticket-creation.json +23 -16
- package/workflows/architecture-scalability-audit.json +29 -22
- package/workflows/bug-investigation.agentic.v2.json +7 -0
- package/workflows/coding-task-workflow-agentic.json +7 -0
- package/workflows/coding-task-workflow-agentic.lean.v2.json +16 -8
- package/workflows/coding-task-workflow-agentic.v2.json +7 -0
- package/workflows/cross-platform-code-conversion.v2.json +7 -0
- package/workflows/document-creation-workflow.json +15 -8
- package/workflows/documentation-update-workflow.json +15 -8
- package/workflows/intelligent-test-case-generation.json +7 -0
- package/workflows/learner-centered-course-workflow.json +9 -2
- package/workflows/mr-review-workflow.agentic.v2.json +7 -0
- package/workflows/personal-learning-materials-creation-branched.json +15 -8
- package/workflows/presentation-creation.json +12 -5
- package/workflows/production-readiness-audit.json +7 -0
- package/workflows/relocation-workflow-us.json +39 -32
- package/workflows/scoped-documentation-workflow.json +33 -26
- package/workflows/ui-ux-design-workflow.json +7 -0
- package/workflows/workflow-diagnose-environment.json +6 -0
- package/workflows/workflow-for-workflows.json +7 -0
- package/workflows/workflow-for-workflows.v2.json +23 -11
- package/workflows/wr.discovery.json +8 -1
- package/dist/console/assets/index-BZYIjrzJ.js +0 -28
- package/dist/console/assets/index-OLCKbDdm.css +0 -1
|
@@ -5,11 +5,6 @@ const neverthrow_1 = require("neverthrow");
|
|
|
5
5
|
const constants_js_1 = require("../durable-core/constants.js");
|
|
6
6
|
const defaultPrefs = { autonomy: 'guided', riskPolicy: 'conservative' };
|
|
7
7
|
function projectPreferencesV2(events, parentByNodeId) {
|
|
8
|
-
for (let i = 1; i < events.length; i++) {
|
|
9
|
-
if (events[i].eventIndex < events[i - 1].eventIndex) {
|
|
10
|
-
return (0, neverthrow_1.err)({ code: 'PROJECTION_INVARIANT_VIOLATION', message: 'Events must be sorted by eventIndex ascending' });
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
8
|
const changesByNodeId = {};
|
|
14
9
|
for (const e of events) {
|
|
15
10
|
if (e.kind !== constants_js_1.EVENT_KIND.PREFERENCES_CHANGED)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Result } from 'neverthrow';
|
|
2
|
-
import type {
|
|
2
|
+
import type { SortedEventLog } from '../durable-core/sorted-event-log.js';
|
|
3
3
|
import type { JsonObject } from '../durable-core/canonical/json-types.js';
|
|
4
4
|
import type { RunId } from '../durable-core/ids/index.js';
|
|
5
5
|
import type { ProjectionError } from './projection-error.js';
|
|
@@ -13,4 +13,4 @@ export interface RunContextV2 {
|
|
|
13
13
|
export interface RunContextProjectionV2 {
|
|
14
14
|
readonly byRunId: Readonly<Record<string, RunContextV2>>;
|
|
15
15
|
}
|
|
16
|
-
export declare function projectRunContextV2(events:
|
|
16
|
+
export declare function projectRunContextV2(events: SortedEventLog): Result<RunContextProjectionV2, ProjectionError>;
|
|
@@ -5,11 +5,6 @@ const neverthrow_1 = require("neverthrow");
|
|
|
5
5
|
const constants_js_1 = require("../durable-core/constants.js");
|
|
6
6
|
const index_js_1 = require("../durable-core/ids/index.js");
|
|
7
7
|
function projectRunContextV2(events) {
|
|
8
|
-
for (let i = 1; i < events.length; i++) {
|
|
9
|
-
if (events[i].eventIndex < events[i - 1].eventIndex) {
|
|
10
|
-
return (0, neverthrow_1.err)({ code: 'PROJECTION_INVARIANT_VIOLATION', message: 'Events must be sorted by eventIndex ascending' });
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
8
|
const byRunId = {};
|
|
14
9
|
for (const e of events) {
|
|
15
10
|
if (e.kind !== constants_js_1.EVENT_KIND.CONTEXT_SET)
|
|
@@ -151,7 +151,13 @@ function projectRunDagV2(events) {
|
|
|
151
151
|
createdAtEventIndex: e.eventIndex,
|
|
152
152
|
};
|
|
153
153
|
if (existing) {
|
|
154
|
-
|
|
154
|
+
const differs = existing.nodeId !== node.nodeId ||
|
|
155
|
+
existing.nodeKind !== node.nodeKind ||
|
|
156
|
+
existing.parentNodeId !== node.parentNodeId ||
|
|
157
|
+
existing.workflowHash !== node.workflowHash ||
|
|
158
|
+
existing.snapshotRef !== node.snapshotRef ||
|
|
159
|
+
existing.createdAtEventIndex !== node.createdAtEventIndex;
|
|
160
|
+
if (differs) {
|
|
155
161
|
return (0, neverthrow_1.err)({
|
|
156
162
|
code: 'PROJECTION_CORRUPTION_DETECTED',
|
|
157
163
|
message: `node_created conflict for runId=${runId} nodeId=${nodeId}`,
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Result } from 'neverthrow';
|
|
2
|
+
import type { DomainEventV1 } from '../durable-core/schemas/session/index.js';
|
|
3
|
+
import type { ProjectionError } from './projection-error.js';
|
|
4
|
+
import type { ConsoleExecutionTraceSummary } from '../usecases/console-types.js';
|
|
5
|
+
export interface RunExecutionTraceProjectionV2 {
|
|
6
|
+
readonly byRunId: Readonly<Record<string, ConsoleExecutionTraceSummary>>;
|
|
7
|
+
}
|
|
8
|
+
export declare function projectRunExecutionTraceV2(events: readonly DomainEventV1[]): Result<RunExecutionTraceProjectionV2, ProjectionError>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.projectRunExecutionTraceV2 = projectRunExecutionTraceV2;
|
|
4
|
+
const neverthrow_1 = require("neverthrow");
|
|
5
|
+
const constants_js_1 = require("../durable-core/constants.js");
|
|
6
|
+
const CONTEXT_KEYS_TO_ELEVATE = ['taskComplexity'];
|
|
7
|
+
function projectRunExecutionTraceV2(events) {
|
|
8
|
+
for (let i = 1; i < events.length; i++) {
|
|
9
|
+
if (events[i].eventIndex < events[i - 1].eventIndex) {
|
|
10
|
+
return (0, neverthrow_1.err)({
|
|
11
|
+
code: 'PROJECTION_INVARIANT_VIOLATION',
|
|
12
|
+
message: 'Events must be sorted by eventIndex ascending',
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const itemsByRunId = {};
|
|
17
|
+
const contextFactsByRunId = {};
|
|
18
|
+
const pushItem = (runId, item) => {
|
|
19
|
+
const existing = itemsByRunId[runId] ?? [];
|
|
20
|
+
existing.push(item);
|
|
21
|
+
itemsByRunId[runId] = existing;
|
|
22
|
+
};
|
|
23
|
+
for (const event of events) {
|
|
24
|
+
switch (event.kind) {
|
|
25
|
+
case constants_js_1.EVENT_KIND.DECISION_TRACE_APPENDED: {
|
|
26
|
+
const runId = event.scope.runId;
|
|
27
|
+
const nodeRef = { kind: 'node_id', value: event.scope.nodeId };
|
|
28
|
+
for (const entry of event.data.entries) {
|
|
29
|
+
pushItem(runId, {
|
|
30
|
+
kind: entry.kind,
|
|
31
|
+
summary: entry.summary,
|
|
32
|
+
recordedAtEventIndex: event.eventIndex,
|
|
33
|
+
refs: [nodeRef, ...mapDecisionTraceRefs(entry.refs)],
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
case constants_js_1.EVENT_KIND.DIVERGENCE_RECORDED: {
|
|
39
|
+
const runId = event.scope.runId;
|
|
40
|
+
const refs = [
|
|
41
|
+
{ kind: 'node_id', value: event.scope.nodeId },
|
|
42
|
+
];
|
|
43
|
+
if (event.data.relatedStepId) {
|
|
44
|
+
refs.push({ kind: 'step_id', value: event.data.relatedStepId });
|
|
45
|
+
}
|
|
46
|
+
pushItem(runId, {
|
|
47
|
+
kind: 'divergence',
|
|
48
|
+
summary: event.data.summary,
|
|
49
|
+
recordedAtEventIndex: event.eventIndex,
|
|
50
|
+
refs,
|
|
51
|
+
});
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
case constants_js_1.EVENT_KIND.CONTEXT_SET: {
|
|
55
|
+
const context = event.data.context;
|
|
56
|
+
if (!context || typeof context !== 'object' || Array.isArray(context)) {
|
|
57
|
+
return (0, neverthrow_1.err)({
|
|
58
|
+
code: 'PROJECTION_CORRUPTION_DETECTED',
|
|
59
|
+
message: `context_set event has invalid context type (runId=${event.scope.runId}, eventId=${event.eventId})`,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
const contextObj = context;
|
|
63
|
+
const facts = CONTEXT_KEYS_TO_ELEVATE.flatMap((key) => {
|
|
64
|
+
const value = contextObj[key];
|
|
65
|
+
if (value === undefined || value === null)
|
|
66
|
+
return [];
|
|
67
|
+
return [{ key, value: stringifyContextValue(value) }];
|
|
68
|
+
});
|
|
69
|
+
if (facts.length > 0) {
|
|
70
|
+
contextFactsByRunId[event.scope.runId] = facts;
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
default:
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const runIds = new Set([
|
|
79
|
+
...Object.keys(itemsByRunId),
|
|
80
|
+
...Object.keys(contextFactsByRunId),
|
|
81
|
+
]);
|
|
82
|
+
const byRunId = {};
|
|
83
|
+
for (const runId of runIds) {
|
|
84
|
+
byRunId[runId] = {
|
|
85
|
+
items: itemsByRunId[runId] ?? [],
|
|
86
|
+
contextFacts: contextFactsByRunId[runId] ?? [],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return (0, neverthrow_1.ok)({ byRunId });
|
|
90
|
+
}
|
|
91
|
+
function mapDecisionTraceRefs(refs) {
|
|
92
|
+
if (!refs || refs.length === 0)
|
|
93
|
+
return [];
|
|
94
|
+
const mapped = [];
|
|
95
|
+
for (const ref of refs) {
|
|
96
|
+
switch (ref.kind) {
|
|
97
|
+
case 'step_id':
|
|
98
|
+
if (ref.stepId) {
|
|
99
|
+
mapped.push({ kind: 'step_id', value: ref.stepId });
|
|
100
|
+
}
|
|
101
|
+
break;
|
|
102
|
+
case 'loop_id':
|
|
103
|
+
if (ref.loopId) {
|
|
104
|
+
mapped.push({ kind: 'loop_id', value: ref.loopId });
|
|
105
|
+
}
|
|
106
|
+
break;
|
|
107
|
+
case 'condition_id':
|
|
108
|
+
if (ref.conditionId) {
|
|
109
|
+
mapped.push({ kind: 'condition_id', value: ref.conditionId });
|
|
110
|
+
}
|
|
111
|
+
break;
|
|
112
|
+
default:
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return mapped;
|
|
117
|
+
}
|
|
118
|
+
function stringifyContextValue(value) {
|
|
119
|
+
if (typeof value === 'string')
|
|
120
|
+
return value;
|
|
121
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
122
|
+
return String(value);
|
|
123
|
+
return JSON.stringify(value);
|
|
124
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Result } from 'neverthrow';
|
|
2
|
-
import type {
|
|
2
|
+
import type { SortedEventLog } from '../durable-core/sorted-event-log.js';
|
|
3
3
|
import type { AutonomyV2, RiskPolicyV2 } from '../durable-core/schemas/session/preferences.js';
|
|
4
4
|
import type { ProjectionError } from './projection-error.js';
|
|
5
5
|
export interface PreferencesSnapshotV2 {
|
|
@@ -16,4 +16,4 @@ export interface RunStatusSignalsV2 {
|
|
|
16
16
|
export interface RunStatusSignalsProjectionV2 {
|
|
17
17
|
readonly byRunId: Readonly<Record<string, RunStatusSignalsV2>>;
|
|
18
18
|
}
|
|
19
|
-
export declare function projectRunStatusSignalsV2(events:
|
|
19
|
+
export declare function projectRunStatusSignalsV2(events: SortedEventLog): Result<RunStatusSignalsProjectionV2, ProjectionError>;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { Application } from 'express';
|
|
2
2
|
import type { ConsoleService } from './console-service.js';
|
|
3
|
-
|
|
3
|
+
import type { WorkflowService } from '../../application/services/workflow-service.js';
|
|
4
|
+
import type { ToolCallTimingRingBuffer } from '../../mcp/tool-call-timing.js';
|
|
5
|
+
export declare function mountConsoleRoutes(app: Application, consoleService: ConsoleService, workflowService?: WorkflowService, timingRingBuffer?: ToolCallTimingRingBuffer): () => void;
|
|
@@ -8,32 +8,18 @@ const express_1 = __importDefault(require("express"));
|
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const fs_1 = __importDefault(require("fs"));
|
|
10
10
|
const worktree_service_js_1 = require("./worktree-service.js");
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
function broadcastChange() {
|
|
14
|
-
if (sseDebounceTimer !== null)
|
|
15
|
-
return;
|
|
16
|
-
sseDebounceTimer = setTimeout(() => {
|
|
17
|
-
sseDebounceTimer = null;
|
|
18
|
-
for (const client of sseClients) {
|
|
19
|
-
try {
|
|
20
|
-
client.write('data: {"type":"change"}\n\n');
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
sseClients.delete(client);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}, 200);
|
|
27
|
-
}
|
|
28
|
-
function watchSessionsDir(sessionsDir) {
|
|
11
|
+
const dev_mode_js_1 = require("../../mcp/dev-mode.js");
|
|
12
|
+
function watchSessionsDir(sessionsDir, onChanged) {
|
|
29
13
|
try {
|
|
30
14
|
fs_1.default.mkdirSync(sessionsDir, { recursive: true });
|
|
31
15
|
}
|
|
32
16
|
catch { }
|
|
33
17
|
let watcher = null;
|
|
34
18
|
try {
|
|
35
|
-
watcher = fs_1.default.watch(sessionsDir, { recursive: true }, () => {
|
|
36
|
-
|
|
19
|
+
watcher = fs_1.default.watch(sessionsDir, { recursive: true }, (_eventType, filename) => {
|
|
20
|
+
if (filename !== null && filename.endsWith('.jsonl')) {
|
|
21
|
+
onChanged();
|
|
22
|
+
}
|
|
37
23
|
});
|
|
38
24
|
watcher.on('error', () => { });
|
|
39
25
|
}
|
|
@@ -53,9 +39,38 @@ function resolveConsoleDist() {
|
|
|
53
39
|
return legacyConsoleDist;
|
|
54
40
|
return null;
|
|
55
41
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
42
|
+
let cachedWorkflowTags = null;
|
|
43
|
+
function loadWorkflowTags() {
|
|
44
|
+
if (cachedWorkflowTags !== null)
|
|
45
|
+
return cachedWorkflowTags;
|
|
46
|
+
const tagsPath = path_1.default.resolve(__dirname, '../../../spec/workflow-tags.json');
|
|
47
|
+
try {
|
|
48
|
+
cachedWorkflowTags = JSON.parse(fs_1.default.readFileSync(tagsPath, 'utf8'));
|
|
49
|
+
return cachedWorkflowTags;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return { version: 0, tags: [], workflows: {} };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function mountConsoleRoutes(app, consoleService, workflowService, timingRingBuffer) {
|
|
56
|
+
const sseClients = new Set();
|
|
57
|
+
let sseDebounceTimer = null;
|
|
58
|
+
function broadcastChange() {
|
|
59
|
+
if (sseDebounceTimer !== null)
|
|
60
|
+
return;
|
|
61
|
+
sseDebounceTimer = setTimeout(() => {
|
|
62
|
+
sseDebounceTimer = null;
|
|
63
|
+
for (const client of sseClients) {
|
|
64
|
+
try {
|
|
65
|
+
client.write('data: {"type":"change"}\n\n');
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
sseClients.delete(client);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}, 200);
|
|
72
|
+
}
|
|
73
|
+
const stopWatcher = watchSessionsDir(consoleService.getSessionsDir(), broadcastChange);
|
|
59
74
|
app.get('/api/v2/workspace/events', (req, res) => {
|
|
60
75
|
res.setHeader('Content-Type', 'text/event-stream');
|
|
61
76
|
res.setHeader('Cache-Control', 'no-cache');
|
|
@@ -67,12 +82,22 @@ function mountConsoleRoutes(app, consoleService) {
|
|
|
67
82
|
req.on('close', () => { sseClients.delete(res); });
|
|
68
83
|
res.on('close', () => { sseClients.delete(res); });
|
|
69
84
|
});
|
|
85
|
+
if (dev_mode_js_1.DEV_MODE) {
|
|
86
|
+
app.get('/api/v2/perf/tool-calls', (req, res) => {
|
|
87
|
+
const rawLimit = req.query['limit'];
|
|
88
|
+
const limit = typeof rawLimit === 'string' ? parseInt(rawLimit, 10) : undefined;
|
|
89
|
+
const safeLimit = (limit !== undefined && Number.isFinite(limit) && limit > 0) ? limit : undefined;
|
|
90
|
+
const observations = timingRingBuffer ? timingRingBuffer.recent(safeLimit) : [];
|
|
91
|
+
res.json({ success: true, data: { observations, total: timingRingBuffer?.size ?? 0, devMode: dev_mode_js_1.DEV_MODE } });
|
|
92
|
+
});
|
|
93
|
+
}
|
|
70
94
|
app.get('/api/v2/sessions', async (_req, res) => {
|
|
71
95
|
const result = await consoleService.getSessionList();
|
|
72
96
|
result.match((data) => res.json({ success: true, data }), (error) => res.status(500).json({ success: false, error: error.message }));
|
|
73
97
|
});
|
|
74
98
|
let cwdRepoRootPromise = null;
|
|
75
99
|
const REPO_ROOTS_TTL_MS = 60000;
|
|
100
|
+
const REPO_ROOT_SESSION_STALENESS_MS = 30 * 24 * 60 * 60 * 1000;
|
|
76
101
|
let cachedRepoRoots = [];
|
|
77
102
|
let repoRootsExpiresAt = 0;
|
|
78
103
|
app.get('/api/v2/worktrees', async (_req, res) => {
|
|
@@ -83,7 +108,11 @@ function mountConsoleRoutes(app, consoleService) {
|
|
|
83
108
|
if (Date.now() > repoRootsExpiresAt) {
|
|
84
109
|
cwdRepoRootPromise ?? (cwdRepoRootPromise = (0, worktree_service_js_1.resolveRepoRoot)(process.cwd()));
|
|
85
110
|
const cwdRoot = await cwdRepoRootPromise;
|
|
86
|
-
const
|
|
111
|
+
const cutoffMs = Date.now() - REPO_ROOT_SESSION_STALENESS_MS;
|
|
112
|
+
const rawRoots = sessions
|
|
113
|
+
.filter(s => s.lastModifiedMs >= cutoffMs)
|
|
114
|
+
.map(s => s.repoRoot)
|
|
115
|
+
.filter((r) => r !== null);
|
|
87
116
|
const resolvedRoots = await Promise.all(rawRoots.map(r => (0, worktree_service_js_1.resolveRepoRoot)(r)));
|
|
88
117
|
const repoRootSet = new Set(resolvedRoots.filter((r) => r !== null));
|
|
89
118
|
if (cwdRoot !== null)
|
|
@@ -116,10 +145,78 @@ function mountConsoleRoutes(app, consoleService) {
|
|
|
116
145
|
res.status(status).json({ success: false, error: error.message });
|
|
117
146
|
});
|
|
118
147
|
});
|
|
148
|
+
if (workflowService) {
|
|
149
|
+
app.get('/api/v2/workflows', async (_req, res) => {
|
|
150
|
+
try {
|
|
151
|
+
const tagsFile = loadWorkflowTags();
|
|
152
|
+
const allWorkflows = await workflowService.loadAllWorkflows();
|
|
153
|
+
const workflows = allWorkflows
|
|
154
|
+
.filter((w) => !tagsFile.workflows[w.definition.id]?.hidden)
|
|
155
|
+
.map((w) => {
|
|
156
|
+
const { definition, source } = w;
|
|
157
|
+
const tagEntry = tagsFile.workflows[definition.id];
|
|
158
|
+
return {
|
|
159
|
+
id: definition.id,
|
|
160
|
+
name: definition.name,
|
|
161
|
+
description: definition.description,
|
|
162
|
+
version: definition.version,
|
|
163
|
+
tags: tagEntry?.tags ?? [],
|
|
164
|
+
source,
|
|
165
|
+
...(definition.about !== undefined ? { about: definition.about } : {}),
|
|
166
|
+
...(definition.examples?.length ? { examples: [...definition.examples] } : {}),
|
|
167
|
+
};
|
|
168
|
+
});
|
|
169
|
+
res.json({ success: true, data: { workflows } });
|
|
170
|
+
}
|
|
171
|
+
catch (e) {
|
|
172
|
+
res.status(500).json({ success: false, error: e instanceof Error ? e.message : String(e) });
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
app.get('/api/v2/workflows/:workflowId', async (req, res) => {
|
|
176
|
+
const { workflowId } = req.params;
|
|
177
|
+
try {
|
|
178
|
+
const workflow = await workflowService.getWorkflowById(workflowId);
|
|
179
|
+
if (!workflow) {
|
|
180
|
+
return res.status(404).json({ success: false, error: `Workflow not found: ${workflowId}` });
|
|
181
|
+
}
|
|
182
|
+
const tagsFile = loadWorkflowTags();
|
|
183
|
+
if (tagsFile.workflows[workflowId]?.hidden) {
|
|
184
|
+
return res.status(404).json({ success: false, error: `Workflow not found: ${workflowId}` });
|
|
185
|
+
}
|
|
186
|
+
const { definition, source } = workflow;
|
|
187
|
+
const tagEntry = tagsFile.workflows[workflowId];
|
|
188
|
+
return res.json({
|
|
189
|
+
success: true,
|
|
190
|
+
data: {
|
|
191
|
+
id: definition.id,
|
|
192
|
+
name: definition.name,
|
|
193
|
+
description: definition.description,
|
|
194
|
+
version: definition.version,
|
|
195
|
+
tags: tagEntry?.tags ?? [],
|
|
196
|
+
source,
|
|
197
|
+
stepCount: definition.steps.length,
|
|
198
|
+
...(definition.about !== undefined ? { about: definition.about } : {}),
|
|
199
|
+
...(definition.examples?.length ? { examples: [...definition.examples] } : {}),
|
|
200
|
+
...(definition.preconditions?.length ? { preconditions: [...definition.preconditions] } : {}),
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
catch (e) {
|
|
205
|
+
return res.status(500).json({ success: false, error: e instanceof Error ? e.message : String(e) });
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
119
209
|
const consoleDist = resolveConsoleDist();
|
|
120
210
|
if (consoleDist) {
|
|
121
|
-
app.use('/console', express_1.default.static(consoleDist
|
|
211
|
+
app.use('/console', express_1.default.static(consoleDist, {
|
|
212
|
+
setHeaders(res, filePath) {
|
|
213
|
+
if (path_1.default.basename(filePath) === 'index.html') {
|
|
214
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}));
|
|
122
218
|
app.get('/console/*path', (_req, res) => {
|
|
219
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
123
220
|
res.sendFile(path_1.default.join(consoleDist, 'index.html'));
|
|
124
221
|
});
|
|
125
222
|
console.error(`[Console] UI serving from ${consoleDist}`);
|
|
@@ -133,4 +230,5 @@ function mountConsoleRoutes(app, consoleService) {
|
|
|
133
230
|
});
|
|
134
231
|
console.error('[Console] UI not found (run: cd console && npm run build)');
|
|
135
232
|
}
|
|
233
|
+
return stopWatcher;
|
|
136
234
|
}
|
|
@@ -15,6 +15,7 @@ export interface ConsoleServicePorts {
|
|
|
15
15
|
export declare class ConsoleService {
|
|
16
16
|
private readonly ports;
|
|
17
17
|
constructor(ports: ConsoleServicePorts);
|
|
18
|
+
private readonly _summaryCache;
|
|
18
19
|
getSessionsDir(): string;
|
|
19
20
|
getSessionList(): ResultAsync<ConsoleSessionListResponse, ConsoleServiceError>;
|
|
20
21
|
getSessionDetail(sessionIdStr: string): ResultAsync<ConsoleSessionDetail, ConsoleServiceError>;
|
|
@@ -11,6 +11,8 @@ const node_outputs_js_1 = require("../projections/node-outputs.js");
|
|
|
11
11
|
const advance_outcomes_js_1 = require("../projections/advance-outcomes.js");
|
|
12
12
|
const artifacts_js_1 = require("../projections/artifacts.js");
|
|
13
13
|
const run_context_js_1 = require("../projections/run-context.js");
|
|
14
|
+
const sorted_event_log_js_1 = require("../durable-core/sorted-event-log.js");
|
|
15
|
+
const run_execution_trace_js_1 = require("../projections/run-execution-trace.js");
|
|
14
16
|
const constants_js_1 = require("../durable-core/constants.js");
|
|
15
17
|
const index_js_1 = require("../durable-core/ids/index.js");
|
|
16
18
|
const MAX_SESSIONS_TO_LOAD = 500;
|
|
@@ -18,6 +20,7 @@ const DORMANCY_THRESHOLD_MS = 3 * 24 * 60 * 60 * 1000;
|
|
|
18
20
|
class ConsoleService {
|
|
19
21
|
constructor(ports) {
|
|
20
22
|
this.ports = ports;
|
|
23
|
+
this._summaryCache = new Map();
|
|
21
24
|
}
|
|
22
25
|
getSessionsDir() {
|
|
23
26
|
return this.ports.dataDir.sessionsDir();
|
|
@@ -103,6 +106,10 @@ class ConsoleService {
|
|
|
103
106
|
});
|
|
104
107
|
}
|
|
105
108
|
loadSessionSummary(sessionId, lastModifiedMs, nowMs) {
|
|
109
|
+
const cached = this._summaryCache.get(sessionId);
|
|
110
|
+
if (cached !== undefined && cached.mtime === lastModifiedMs) {
|
|
111
|
+
return (0, neverthrow_2.okAsync)(cached.summary);
|
|
112
|
+
}
|
|
106
113
|
return this.ports.sessionStore
|
|
107
114
|
.load(sessionId)
|
|
108
115
|
.andThen((truth) => {
|
|
@@ -110,10 +117,22 @@ class ConsoleService {
|
|
|
110
117
|
const workflowNamesRA = dagRes.isOk()
|
|
111
118
|
? resolveWorkflowNames(dagRes.value, this.ports.pinnedWorkflowStore)
|
|
112
119
|
: (0, neverthrow_2.okAsync)({});
|
|
120
|
+
const completionRA = dagRes.isOk()
|
|
121
|
+
? resolveRunCompletionFromDag(dagRes.value, this.ports.snapshotStore)
|
|
122
|
+
: (0, neverthrow_2.okAsync)({});
|
|
113
123
|
return neverthrow_1.ResultAsync.combine([
|
|
114
|
-
|
|
124
|
+
completionRA,
|
|
115
125
|
workflowNamesRA,
|
|
116
|
-
]).map(([completionMap, workflowNames]) =>
|
|
126
|
+
]).map(([completionMap, workflowNames]) => {
|
|
127
|
+
const dag = dagRes.isOk() ? dagRes.value : undefined;
|
|
128
|
+
return projectSessionSummary(sessionId, truth, completionMap, workflowNames, lastModifiedMs, nowMs, dag);
|
|
129
|
+
});
|
|
130
|
+
})
|
|
131
|
+
.map((summary) => {
|
|
132
|
+
if (summary !== null && summary.status !== 'in_progress') {
|
|
133
|
+
this._summaryCache.set(sessionId, { mtime: lastModifiedMs, summary });
|
|
134
|
+
}
|
|
135
|
+
return summary;
|
|
117
136
|
})
|
|
118
137
|
.orElse(() => (0, neverthrow_2.okAsync)(null));
|
|
119
138
|
}
|
|
@@ -239,8 +258,8 @@ function extractStepTitlesFromCompiled(compiled) {
|
|
|
239
258
|
return titles;
|
|
240
259
|
}
|
|
241
260
|
const TITLE_CONTEXT_KEYS = ['goal', 'taskDescription', 'mrTitle', 'prTitle', 'ticketTitle', 'problem'];
|
|
242
|
-
function deriveSessionTitle(
|
|
243
|
-
const contextRes = (0, run_context_js_1.projectRunContextV2)(
|
|
261
|
+
function deriveSessionTitle(sortedEvents) {
|
|
262
|
+
const contextRes = (0, run_context_js_1.projectRunContextV2)(sortedEvents);
|
|
244
263
|
if (contextRes.isOk()) {
|
|
245
264
|
for (const runCtx of Object.values(contextRes.value.byRunId)) {
|
|
246
265
|
for (const key of TITLE_CONTEXT_KEYS) {
|
|
@@ -251,7 +270,7 @@ function deriveSessionTitle(events) {
|
|
|
251
270
|
}
|
|
252
271
|
}
|
|
253
272
|
}
|
|
254
|
-
const title = extractTitleFromFirstRecap(
|
|
273
|
+
const title = extractTitleFromFirstRecap(sortedEvents);
|
|
255
274
|
if (title)
|
|
256
275
|
return title;
|
|
257
276
|
return null;
|
|
@@ -333,19 +352,26 @@ function truncateTitle(text, maxLen = 120) {
|
|
|
333
352
|
return text;
|
|
334
353
|
return text.slice(0, maxLen - 1) + '…';
|
|
335
354
|
}
|
|
336
|
-
function projectSessionSummary(sessionId, truth, completionByRunId, workflowNames, lastModifiedMs, nowMs) {
|
|
355
|
+
function projectSessionSummary(sessionId, truth, completionByRunId, workflowNames, lastModifiedMs, nowMs, precomputedDag) {
|
|
337
356
|
const { events } = truth;
|
|
338
357
|
const health = (0, session_health_js_1.projectSessionHealthV2)(truth);
|
|
339
358
|
if (health.isErr())
|
|
340
359
|
return null;
|
|
341
360
|
const sessionHealth = health.value.kind === 'healthy' ? 'healthy' : 'corrupt';
|
|
342
|
-
|
|
343
|
-
if (
|
|
361
|
+
let dag;
|
|
362
|
+
if (precomputedDag !== undefined) {
|
|
363
|
+
dag = precomputedDag;
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
const res = (0, run_dag_js_1.projectRunDagV2)(events);
|
|
367
|
+
dag = res.isOk() ? res.value : null;
|
|
368
|
+
}
|
|
369
|
+
if (dag === null)
|
|
344
370
|
return null;
|
|
345
|
-
const
|
|
346
|
-
const statusRes = (0, run_status_signals_js_1.projectRunStatusSignalsV2)(
|
|
347
|
-
const gapsRes = (0, gaps_js_1.projectGapsV2)(
|
|
348
|
-
const sessionTitle = deriveSessionTitle(
|
|
371
|
+
const sortedEventsRes = (0, sorted_event_log_js_1.asSortedEventLog)(events);
|
|
372
|
+
const statusRes = sortedEventsRes.isOk() ? (0, run_status_signals_js_1.projectRunStatusSignalsV2)(sortedEventsRes.value) : (0, neverthrow_2.err)(sortedEventsRes.error);
|
|
373
|
+
const gapsRes = sortedEventsRes.isOk() ? (0, gaps_js_1.projectGapsV2)(sortedEventsRes.value) : (0, neverthrow_2.err)(sortedEventsRes.error);
|
|
374
|
+
const sessionTitle = sortedEventsRes.isOk() ? deriveSessionTitle(sortedEventsRes.value) : null;
|
|
349
375
|
const gitBranch = extractGitBranch(events);
|
|
350
376
|
const repoRoot = extractRepoRoot(events);
|
|
351
377
|
const runs = Object.values(dag.runsById);
|
|
@@ -418,26 +444,52 @@ function projectSessionDetail(sessionId, truth, completionByRunId, stepLabels, w
|
|
|
418
444
|
const { events } = truth;
|
|
419
445
|
const health = (0, session_health_js_1.projectSessionHealthV2)(truth);
|
|
420
446
|
const sessionHealth = health.isOk() && health.value.kind === 'healthy' ? 'healthy' : 'corrupt';
|
|
421
|
-
const
|
|
447
|
+
const sortedEventsRes = (0, sorted_event_log_js_1.asSortedEventLog)(events);
|
|
448
|
+
const sessionTitle = sortedEventsRes.isOk() ? deriveSessionTitle(sortedEventsRes.value) : null;
|
|
422
449
|
const dagRes = (0, run_dag_js_1.projectRunDagV2)(events);
|
|
423
450
|
if (dagRes.isErr()) {
|
|
424
451
|
return { sessionId, sessionTitle, health: sessionHealth, runs: [] };
|
|
425
452
|
}
|
|
426
|
-
const statusRes = (0, run_status_signals_js_1.projectRunStatusSignalsV2)(
|
|
427
|
-
const gapsRes = (0, gaps_js_1.projectGapsV2)(
|
|
453
|
+
const statusRes = sortedEventsRes.isOk() ? (0, run_status_signals_js_1.projectRunStatusSignalsV2)(sortedEventsRes.value) : (0, neverthrow_2.err)(sortedEventsRes.error);
|
|
454
|
+
const gapsRes = sortedEventsRes.isOk() ? (0, gaps_js_1.projectGapsV2)(sortedEventsRes.value) : (0, neverthrow_2.err)(sortedEventsRes.error);
|
|
455
|
+
const executionTraceRes = (0, run_execution_trace_js_1.projectRunExecutionTraceV2)(events);
|
|
456
|
+
const outputsRes = (0, node_outputs_js_1.projectNodeOutputsV2)(events);
|
|
457
|
+
const artifactsRes = (0, artifacts_js_1.projectArtifactsV2)(events);
|
|
458
|
+
const failedValidationNodeIds = new Set();
|
|
459
|
+
for (const e of events) {
|
|
460
|
+
if (e.kind !== constants_js_1.EVENT_KIND.VALIDATION_PERFORMED)
|
|
461
|
+
continue;
|
|
462
|
+
if (!e.data.result.valid) {
|
|
463
|
+
failedValidationNodeIds.add(e.scope.nodeId);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
const gapNodeIds = new Set();
|
|
467
|
+
if (gapsRes.isOk()) {
|
|
468
|
+
for (const gap of Object.values(gapsRes.value.byGapId)) {
|
|
469
|
+
gapNodeIds.add(gap.nodeId);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
428
472
|
const runs = Object.values(dagRes.value.runsById).map((run) => {
|
|
429
473
|
const statusSignals = statusRes.isOk() ? statusRes.value.byRunId[run.runId] : undefined;
|
|
430
474
|
const status = deriveRunStatus(statusSignals?.isBlocked ?? false, statusSignals?.hasUnresolvedCriticalGaps ?? false, completionByRunId[run.runId] ?? false);
|
|
431
475
|
const tipSet = new Set(run.tipNodeIds);
|
|
432
|
-
const nodes = Object.values(run.nodesById).map((node) =>
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
476
|
+
const nodes = Object.values(run.nodesById).map((node) => {
|
|
477
|
+
const nodeOutputs = outputsRes.isOk() ? outputsRes.value.nodesById[node.nodeId] : undefined;
|
|
478
|
+
const nodeArtifacts = artifactsRes.isOk() ? artifactsRes.value.byNodeId[node.nodeId] : undefined;
|
|
479
|
+
return {
|
|
480
|
+
nodeId: node.nodeId,
|
|
481
|
+
nodeKind: node.nodeKind,
|
|
482
|
+
parentNodeId: node.parentNodeId,
|
|
483
|
+
createdAtEventIndex: node.createdAtEventIndex,
|
|
484
|
+
isPreferredTip: node.nodeId === run.preferredTipNodeId,
|
|
485
|
+
isTip: tipSet.has(node.nodeId),
|
|
486
|
+
stepLabel: stepLabels[node.nodeId] ?? null,
|
|
487
|
+
hasRecap: (nodeOutputs?.currentByChannel.recap.length ?? 0) > 0,
|
|
488
|
+
hasFailedValidations: failedValidationNodeIds.has(node.nodeId),
|
|
489
|
+
hasGaps: gapNodeIds.has(node.nodeId),
|
|
490
|
+
hasArtifacts: (nodeArtifacts?.artifacts.length ?? 0) > 0,
|
|
491
|
+
};
|
|
492
|
+
});
|
|
441
493
|
const edges = run.edges.map((edge) => ({
|
|
442
494
|
edgeKind: edge.edgeKind,
|
|
443
495
|
fromNodeId: edge.fromNodeId,
|
|
@@ -459,6 +511,9 @@ function projectSessionDetail(sessionId, truth, completionByRunId, stepLabels, w
|
|
|
459
511
|
hasUnresolvedCriticalGaps: gapsRes.isOk()
|
|
460
512
|
? (gapsRes.value.unresolvedCriticalByRunId[run.runId]?.length ?? 0) > 0
|
|
461
513
|
: false,
|
|
514
|
+
executionTraceSummary: executionTraceRes.isOk()
|
|
515
|
+
? (executionTraceRes.value.byRunId[run.runId] ?? null)
|
|
516
|
+
: null,
|
|
462
517
|
};
|
|
463
518
|
});
|
|
464
519
|
return { sessionId, sessionTitle, health: sessionHealth, runs };
|
|
@@ -566,7 +621,10 @@ function extractValidations(events, nodeId) {
|
|
|
566
621
|
return results;
|
|
567
622
|
}
|
|
568
623
|
function extractGaps(events, nodeId) {
|
|
569
|
-
const
|
|
624
|
+
const sortedEventsRes = (0, sorted_event_log_js_1.asSortedEventLog)(events);
|
|
625
|
+
if (sortedEventsRes.isErr())
|
|
626
|
+
return [];
|
|
627
|
+
const gapsRes = (0, gaps_js_1.projectGapsV2)(sortedEventsRes.value);
|
|
570
628
|
if (gapsRes.isErr())
|
|
571
629
|
return [];
|
|
572
630
|
const gaps = [];
|