@xemahq/kernel-contracts 0.30.0 → 0.31.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/decision-gate/index.d.ts +3 -0
- package/dist/decision-gate/index.d.ts.map +1 -0
- package/dist/decision-gate/index.js +19 -0
- package/dist/decision-gate/index.js.map +1 -0
- package/dist/decision-gate/lib/decision-gate.d.ts +55 -0
- package/dist/decision-gate/lib/decision-gate.d.ts.map +1 -0
- package/dist/decision-gate/lib/decision-gate.js +56 -0
- package/dist/decision-gate/lib/decision-gate.js.map +1 -0
- package/dist/decision-gate/lib/pinned-subject-artifact.d.ts +9 -0
- package/dist/decision-gate/lib/pinned-subject-artifact.d.ts.map +1 -0
- package/dist/decision-gate/lib/pinned-subject-artifact.js +11 -0
- package/dist/decision-gate/lib/pinned-subject-artifact.js.map +1 -0
- package/dist/question/index.d.ts +5 -0
- package/dist/question/index.d.ts.map +1 -0
- package/dist/question/index.js +21 -0
- package/dist/question/index.js.map +1 -0
- package/dist/question/lib/question-action-context.d.ts +7 -0
- package/dist/question/lib/question-action-context.d.ts.map +1 -0
- package/dist/question/lib/question-action-context.js +3 -0
- package/dist/question/lib/question-action-context.js.map +1 -0
- package/dist/question/lib/question-dto.d.ts +54 -0
- package/dist/question/lib/question-dto.d.ts.map +1 -0
- package/dist/question/lib/question-dto.js +3 -0
- package/dist/question/lib/question-dto.js.map +1 -0
- package/dist/question/lib/question-enums.d.ts +20 -0
- package/dist/question/lib/question-enums.d.ts.map +1 -0
- package/dist/question/lib/question-enums.js +32 -0
- package/dist/question/lib/question-enums.js.map +1 -0
- package/dist/question/lib/question-escalation.d.ts +8 -0
- package/dist/question/lib/question-escalation.d.ts.map +1 -0
- package/dist/question/lib/question-escalation.js +3 -0
- package/dist/question/lib/question-escalation.js.map +1 -0
- package/dist/review-inquiry/index.d.ts +3 -0
- package/dist/review-inquiry/index.d.ts.map +1 -0
- package/dist/review-inquiry/index.js +19 -0
- package/dist/review-inquiry/index.js.map +1 -0
- package/dist/review-inquiry/lib/review-inquiry.d.ts +138 -0
- package/dist/review-inquiry/lib/review-inquiry.d.ts.map +1 -0
- package/dist/review-inquiry/lib/review-inquiry.js +20 -0
- package/dist/review-inquiry/lib/review-inquiry.js.map +1 -0
- package/dist/review-inquiry/lib/web-annotation.d.ts +195 -0
- package/dist/review-inquiry/lib/web-annotation.d.ts.map +1 -0
- package/dist/review-inquiry/lib/web-annotation.js +59 -0
- package/dist/review-inquiry/lib/web-annotation.js.map +1 -0
- package/dist/run-detail/index.d.ts +7 -0
- package/dist/run-detail/index.d.ts.map +1 -0
- package/dist/run-detail/index.js +23 -0
- package/dist/run-detail/index.js.map +1 -0
- package/dist/run-detail/lib/run-detail-dto.d.ts +34 -0
- package/dist/run-detail/lib/run-detail-dto.d.ts.map +1 -0
- package/dist/run-detail/lib/run-detail-dto.js +3 -0
- package/dist/run-detail/lib/run-detail-dto.js.map +1 -0
- package/dist/run-detail/lib/run-mode.d.ts +14 -0
- package/dist/run-detail/lib/run-mode.d.ts.map +1 -0
- package/dist/run-detail/lib/run-mode.js +25 -0
- package/dist/run-detail/lib/run-mode.js.map +1 -0
- package/dist/run-detail/lib/run-outcome.d.ts +28 -0
- package/dist/run-detail/lib/run-outcome.d.ts.map +1 -0
- package/dist/run-detail/lib/run-outcome.js +27 -0
- package/dist/run-detail/lib/run-outcome.js.map +1 -0
- package/dist/run-detail/lib/run-phase-rail.d.ts +21 -0
- package/dist/run-detail/lib/run-phase-rail.d.ts.map +1 -0
- package/dist/run-detail/lib/run-phase-rail.js +14 -0
- package/dist/run-detail/lib/run-phase-rail.js.map +1 -0
- package/dist/run-detail/lib/run-revision-state.d.ts +47 -0
- package/dist/run-detail/lib/run-revision-state.d.ts.map +1 -0
- package/dist/run-detail/lib/run-revision-state.js +11 -0
- package/dist/run-detail/lib/run-revision-state.js.map +1 -0
- package/dist/run-detail/lib/wait-windows.d.ts +15 -0
- package/dist/run-detail/lib/wait-windows.d.ts.map +1 -0
- package/dist/run-detail/lib/wait-windows.js +39 -0
- package/dist/run-detail/lib/wait-windows.js.map +1 -0
- package/package.json +1 -1
- package/src/decision-gate/index.ts +13 -0
- package/src/decision-gate/lib/decision-gate.ts +113 -0
- package/src/decision-gate/lib/pinned-subject-artifact.ts +29 -0
- package/src/question/index.ts +24 -0
- package/src/question/lib/question-action-context.ts +26 -0
- package/src/question/lib/question-dto.ts +143 -0
- package/src/question/lib/question-enums.ts +67 -0
- package/src/question/lib/question-escalation.ts +22 -0
- package/src/review-inquiry/index.ts +19 -0
- package/src/review-inquiry/lib/review-inquiry.ts +76 -0
- package/src/review-inquiry/lib/web-annotation.ts +136 -0
- package/src/run-detail/index.ts +22 -0
- package/src/run-detail/lib/run-detail-dto.ts +84 -0
- package/src/run-detail/lib/run-mode.ts +65 -0
- package/src/run-detail/lib/run-outcome.ts +66 -0
- package/src/run-detail/lib/run-phase-rail.ts +43 -0
- package/src/run-detail/lib/run-revision-state.ts +83 -0
- package/src/run-detail/lib/wait-windows.ts +70 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* W3C Web Annotation Data Model — Zod schemas for the subset we use as
|
|
5
|
+
* anchor references on REVIEW inquiry comments.
|
|
6
|
+
*
|
|
7
|
+
* Spec: https://www.w3.org/TR/annotation-model/ (W3C Recommendation,
|
|
8
|
+
* 2017). The standard models commentary on web resources via a
|
|
9
|
+
* { body, target, selector } triple and is the de-facto substrate for
|
|
10
|
+
* Hypothesis-style anchored comments.
|
|
11
|
+
*
|
|
12
|
+
* We adopt the standard rather than inventing a custom shape so the
|
|
13
|
+
* platform can integrate with annotation-aware tools (Hypothesis,
|
|
14
|
+
* Apache Annotator, web-annotation-js) without translation, and so a
|
|
15
|
+
* downstream LLM redraft agent receives a vocabulary it already knows.
|
|
16
|
+
*
|
|
17
|
+
* The selector vocabulary below carries every anchor kind a markdown /
|
|
18
|
+
* code / JSON document reasonably needs:
|
|
19
|
+
*
|
|
20
|
+
* - `TextQuoteSelector` — anchor by literal text + prefix/suffix
|
|
21
|
+
* context. Robust against minor edits.
|
|
22
|
+
* - `TextPositionSelector` — char-offset range. Precise but fragile.
|
|
23
|
+
* - `FragmentSelector` — anchor by fragment id (heading slug).
|
|
24
|
+
* - `RangeSelector` — composite (start + end selectors).
|
|
25
|
+
* - `xema:JsonPointerSelector` — RFC 6901 pointer for structured JSON
|
|
26
|
+
* artifacts. Namespaced per W3C extension
|
|
27
|
+
* rules (custom selector `type` strings
|
|
28
|
+
* MUST be IRIs or namespaced identifiers).
|
|
29
|
+
*
|
|
30
|
+
* All selectors carry a discriminating `type` field so the FE / agent
|
|
31
|
+
* can branch on shape without sniffing other fields — standard
|
|
32
|
+
* pattern for the W3C JSON serialisation.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
export const TextQuoteSelectorSchema = z.object({
|
|
36
|
+
type: z.literal('TextQuoteSelector'),
|
|
37
|
+
exact: z.string().min(1),
|
|
38
|
+
prefix: z.string().optional(),
|
|
39
|
+
suffix: z.string().optional(),
|
|
40
|
+
});
|
|
41
|
+
export type TextQuoteSelector = z.infer<typeof TextQuoteSelectorSchema>;
|
|
42
|
+
|
|
43
|
+
export const TextPositionSelectorSchema = z
|
|
44
|
+
.object({
|
|
45
|
+
type: z.literal('TextPositionSelector'),
|
|
46
|
+
start: z.number().int().min(0),
|
|
47
|
+
end: z.number().int().min(0),
|
|
48
|
+
})
|
|
49
|
+
.refine((v) => v.end >= v.start, {
|
|
50
|
+
message: 'TextPositionSelector requires end >= start',
|
|
51
|
+
path: ['end'],
|
|
52
|
+
});
|
|
53
|
+
export type TextPositionSelector = z.infer<typeof TextPositionSelectorSchema>;
|
|
54
|
+
|
|
55
|
+
export const FragmentSelectorSchema = z.object({
|
|
56
|
+
type: z.literal('FragmentSelector'),
|
|
57
|
+
value: z.string().min(1),
|
|
58
|
+
/**
|
|
59
|
+
* Optional `conformsTo` per W3C We only emit two values
|
|
60
|
+
* today: the RFC 3986 IRI for generic fragments and a Xema-specific
|
|
61
|
+
* marker for GFM heading slugs (so consumers know to slugify
|
|
62
|
+
* candidate headings the same way).
|
|
63
|
+
*/
|
|
64
|
+
conformsTo: z
|
|
65
|
+
.union([
|
|
66
|
+
z.literal('https://tools.ietf.org/html/rfc3986'),
|
|
67
|
+
z.literal('gfm-heading-slug'),
|
|
68
|
+
])
|
|
69
|
+
.optional(),
|
|
70
|
+
});
|
|
71
|
+
export type FragmentSelector = z.infer<typeof FragmentSelectorSchema>;
|
|
72
|
+
|
|
73
|
+
export const JsonPointerSelectorSchema = z.object({
|
|
74
|
+
/**
|
|
75
|
+
* Xema-namespaced custom selector type. NOT a core W3C type — but
|
|
76
|
+
* the spec explicitly permits extension types as long as `type`
|
|
77
|
+
* is an IRI or namespaced identifier. Documenting the
|
|
78
|
+
* extension URI is on us.
|
|
79
|
+
*/
|
|
80
|
+
type: z.literal('xema:JsonPointerSelector'),
|
|
81
|
+
/** RFC 6901-compliant JSON Pointer (e.g. `/decisions/2/rationale`). */
|
|
82
|
+
pointer: z.string().min(1),
|
|
83
|
+
});
|
|
84
|
+
export type JsonPointerSelector = z.infer<typeof JsonPointerSelectorSchema>;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Non-recursive selector — everything except RangeSelector. Exported
|
|
88
|
+
* so RangeSelector can use it as its start/end child without inviting
|
|
89
|
+
* arbitrary nesting (a Range of Ranges is technically valid per the
|
|
90
|
+
* W3C spec but adds no expressive power for our use case).
|
|
91
|
+
*/
|
|
92
|
+
export const NonRangeSelectorSchema = z.discriminatedUnion('type', [
|
|
93
|
+
TextQuoteSelectorSchema,
|
|
94
|
+
TextPositionSelectorSchema,
|
|
95
|
+
FragmentSelectorSchema,
|
|
96
|
+
JsonPointerSelectorSchema,
|
|
97
|
+
]);
|
|
98
|
+
export type NonRangeSelector = z.infer<typeof NonRangeSelectorSchema>;
|
|
99
|
+
|
|
100
|
+
export const RangeSelectorSchema = z.object({
|
|
101
|
+
type: z.literal('RangeSelector'),
|
|
102
|
+
startSelector: NonRangeSelectorSchema,
|
|
103
|
+
endSelector: NonRangeSelectorSchema,
|
|
104
|
+
});
|
|
105
|
+
export type RangeSelector = z.infer<typeof RangeSelectorSchema>;
|
|
106
|
+
|
|
107
|
+
export const WebAnnotationSelectorSchema = z.discriminatedUnion('type', [
|
|
108
|
+
TextQuoteSelectorSchema,
|
|
109
|
+
TextPositionSelectorSchema,
|
|
110
|
+
FragmentSelectorSchema,
|
|
111
|
+
JsonPointerSelectorSchema,
|
|
112
|
+
RangeSelectorSchema,
|
|
113
|
+
]);
|
|
114
|
+
export type WebAnnotationSelector = z.infer<typeof WebAnnotationSelectorSchema>;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* W3C Web Annotation Target.
|
|
118
|
+
*
|
|
119
|
+
* Identifies WHICH artifact version the comment refers to and
|
|
120
|
+
* (optionally) WHERE inside it. We model the W3C `source` IRI as the
|
|
121
|
+
* concrete identity triple `{artifactId, versionId, version}` because
|
|
122
|
+
* the platform's artifact-store identity is what downstream consumers
|
|
123
|
+
* actually need — emitting a synthetic URI here would just push
|
|
124
|
+
* parsing complexity onto every reader.
|
|
125
|
+
*
|
|
126
|
+
* `selector` is optional: a whole-document anchor (the reviewer
|
|
127
|
+
* commented on the artifact in general, not a specific span) is a
|
|
128
|
+
* legitimate shape.
|
|
129
|
+
*/
|
|
130
|
+
export const WebAnnotationTargetSchema = z.object({
|
|
131
|
+
artifactId: z.string().min(1),
|
|
132
|
+
versionId: z.string().min(1),
|
|
133
|
+
version: z.number().int().min(1),
|
|
134
|
+
selector: WebAnnotationSelectorSchema.optional(),
|
|
135
|
+
});
|
|
136
|
+
export type WebAnnotationTarget = z.infer<typeof WebAnnotationTargetSchema>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// @xemahq/run-detail-contracts — Layer 1 / Kernel
|
|
3
|
+
//
|
|
4
|
+
// Wire shapes for the workflow run detail surface (FE) and the engine's
|
|
5
|
+
// `GET /workflows/:slug/runs/:id/full` endpoint that powers it.
|
|
6
|
+
//
|
|
7
|
+
// The run detail page collapses into
|
|
8
|
+
// THREE modes — Overview / Work / Developer. The contracts here define what
|
|
9
|
+
// each mode reads, the deterministic mode-default rules, the phase rail, the
|
|
10
|
+
// outcome strip for terminal runs, and the revision-state discriminator that
|
|
11
|
+
// tells the FE which (if any) of the three revision UIs to render.
|
|
12
|
+
//
|
|
13
|
+
// Runtime-agnostic. Pure types. Zero NestJS / Prisma / framework imports.
|
|
14
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
15
|
+
|
|
16
|
+
export * from './lib/run-mode';
|
|
17
|
+
export * from './lib/run-outcome';
|
|
18
|
+
export * from './lib/run-revision-state';
|
|
19
|
+
export * from './lib/run-phase-rail';
|
|
20
|
+
export * from './lib/run-detail-dto';
|
|
21
|
+
export * from './lib/wait-windows';
|
|
22
|
+
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { WorkflowRunStatus } from '../../workflow';
|
|
2
|
+
|
|
3
|
+
import { RunOutcomeDto } from './run-outcome';
|
|
4
|
+
import { RunPhaseRailDto } from './run-phase-rail';
|
|
5
|
+
import { RunRevisionPayload } from './run-revision-state';
|
|
6
|
+
import { WaitWindow } from './wait-windows';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The single payload `GET /workflows/:slug/runs/:id/full` returns. Replaces
|
|
10
|
+
* the legacy `useRunSummary` + `useRunDescribe` + first-page `useRunHistory`
|
|
11
|
+
* + `useRunJobs` quad-query with one coalesced call.
|
|
12
|
+
*
|
|
13
|
+
* Conventions:
|
|
14
|
+
* - Every nested list is bounded — the FE never asks for "all events".
|
|
15
|
+
* Detail/history surfaces page through the timeline endpoint.
|
|
16
|
+
* - Every closed-domain field uses an enum from this package or
|
|
17
|
+
* `../../workflow`. No free-form strings except labels.
|
|
18
|
+
* - The payload is shaped for the user-visible view, not internal
|
|
19
|
+
* state. Internal Temporal IDs are exposed only in DEVELOPER mode.
|
|
20
|
+
*
|
|
21
|
+
* Wire envelope: this DTO is the `data` member of the standard envelope
|
|
22
|
+
*. The FE's envelope-assert util validates the
|
|
23
|
+
* shape end-to-end.
|
|
24
|
+
*/
|
|
25
|
+
export interface RunDetailDto {
|
|
26
|
+
readonly id: string;
|
|
27
|
+
readonly workflowSlug: string;
|
|
28
|
+
readonly workflowDisplayName: string;
|
|
29
|
+
readonly status: WorkflowRunStatus;
|
|
30
|
+
/** Initiator displayName — `null` for system-triggered runs (schedules, webhooks). */
|
|
31
|
+
readonly initiatorDisplayName: string | null;
|
|
32
|
+
readonly startedAt: string;
|
|
33
|
+
/** ISO-8601 when the run reached a terminal state, else `null`. */
|
|
34
|
+
readonly endedAt: string | null;
|
|
35
|
+
/** Engine ETA for in-flight runs. `null` when terminal or unknown. */
|
|
36
|
+
readonly etaAt: string | null;
|
|
37
|
+
|
|
38
|
+
readonly phaseRail: RunPhaseRailDto;
|
|
39
|
+
readonly waitWindows: readonly WaitWindow[];
|
|
40
|
+
|
|
41
|
+
/** What's actively running right now — feeds the Overview "Activity now" strip. */
|
|
42
|
+
readonly activityNow: readonly RunActivityNowDto[];
|
|
43
|
+
|
|
44
|
+
/** Last 6 events (coarse milestones). Click-through navigates to Developer mode. */
|
|
45
|
+
readonly recentTail: readonly RunRecentEventDto[];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Revision-state discriminator + payload (see {@link RunRevisionPayload}).
|
|
49
|
+
* Drives whether/which of the 3 revision UIs renders.
|
|
50
|
+
*/
|
|
51
|
+
readonly revision: RunRevisionPayload;
|
|
52
|
+
|
|
53
|
+
/** Outcome strip for terminal runs. `null` for in-flight. */
|
|
54
|
+
readonly outcome: RunOutcomeDto | null;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* One-line "what's blocking" callout for in-flight runs. Server-computed
|
|
58
|
+
* from waitWindows + escalation state. `null` when nothing is blocking.
|
|
59
|
+
*/
|
|
60
|
+
readonly blockingSummary: string | null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* One row in the Overview "Activity now" strip — coarse "who is doing
|
|
65
|
+
* what right now". Updated via SSE incremental patches; one sentence
|
|
66
|
+
* per active job, no timestamps (they're current).
|
|
67
|
+
*/
|
|
68
|
+
export interface RunActivityNowDto {
|
|
69
|
+
readonly jobKey: string;
|
|
70
|
+
readonly agentDisplayName: string | null;
|
|
71
|
+
readonly summary: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* One row in the Overview "Recent" tail — the last 6 coarse milestones
|
|
76
|
+
* (phase entered, deliverable emitted, agent paused, decision needed).
|
|
77
|
+
*/
|
|
78
|
+
export interface RunRecentEventDto {
|
|
79
|
+
readonly id: string;
|
|
80
|
+
readonly at: string;
|
|
81
|
+
readonly summary: string;
|
|
82
|
+
/** Optional deep-link anchor for the Developer-mode timeline. */
|
|
83
|
+
readonly developerAnchor: string | null;
|
|
84
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { WorkflowRunStatus, TERMINAL_WORKFLOW_RUN_STATUSES } from '../../workflow';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The calm modes that replace the legacy 10-tab run detail.
|
|
5
|
+
*
|
|
6
|
+
* - `OVERVIEW` — coarse status + latest deliverables (links) + last 6
|
|
7
|
+
* events. Default for running and terminal runs.
|
|
8
|
+
* - `WORK` — the question canvas + reply form. Default when the run has
|
|
9
|
+
* a question waiting on the current user.
|
|
10
|
+
* - `OUTPUTS` — full deliverables surface. One sub-tab per artifact;
|
|
11
|
+
* each sub-tab renders the artifact's CONTENT (markdown rendered as
|
|
12
|
+
* an A4 page, code diff via the diff viewer, diagram via mermaid,
|
|
13
|
+
* etc.) with a `Show raw` toggle that reveals the underlying artifact
|
|
14
|
+
* JSON. Never the default — opened from the Overview deliverables
|
|
15
|
+
* strip or directly via the mode switcher.
|
|
16
|
+
* - `DEVELOPER` — full activity console + Temporal history + Mounts /
|
|
17
|
+
* Pending / Query diagnostic cards. Behind a user-pref + role gate.
|
|
18
|
+
*
|
|
19
|
+
* Mode-default selection is deterministic — see {@link selectDefaultRunMode}.
|
|
20
|
+
* Free-text strings are forbidden; consumers route on this enum.
|
|
21
|
+
*/
|
|
22
|
+
export enum RunMode {
|
|
23
|
+
OVERVIEW = 'overview',
|
|
24
|
+
WORK = 'work',
|
|
25
|
+
OUTPUTS = 'outputs',
|
|
26
|
+
DEVELOPER = 'developer',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Inputs the deterministic mode-default selector consumes. Keeping this
|
|
31
|
+
* narrow (no full RunDetailDto reference) lets callers pre-compute it
|
|
32
|
+
* without holding the whole run payload in scope.
|
|
33
|
+
*/
|
|
34
|
+
export interface RunModeSelectionInputs {
|
|
35
|
+
readonly status: WorkflowRunStatus;
|
|
36
|
+
readonly hasQuestionForCurrentUser: boolean;
|
|
37
|
+
/** User-pref override; wins if set. */
|
|
38
|
+
readonly userPreferredMode?: RunMode;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Deterministic mode-default rules:
|
|
43
|
+
*
|
|
44
|
+
* status == waiting_on_human && hasQuestionForMe(runId) → WORK
|
|
45
|
+
* status terminal → OVERVIEW
|
|
46
|
+
* otherwise → OVERVIEW
|
|
47
|
+
* user pref override → wins
|
|
48
|
+
*
|
|
49
|
+
* No timing, no probabilities, no fuzzy matching. The FE calls this once
|
|
50
|
+
* per render and routes accordingly; reverse calls are not supported.
|
|
51
|
+
*/
|
|
52
|
+
export function selectDefaultRunMode(input: RunModeSelectionInputs): RunMode {
|
|
53
|
+
if (input.userPreferredMode !== undefined) {
|
|
54
|
+
return input.userPreferredMode;
|
|
55
|
+
}
|
|
56
|
+
const isTerminal = TERMINAL_WORKFLOW_RUN_STATUSES.includes(input.status);
|
|
57
|
+
if (
|
|
58
|
+
!isTerminal &&
|
|
59
|
+
input.status === WorkflowRunStatus.WAITING_ON_HUMAN &&
|
|
60
|
+
input.hasQuestionForCurrentUser
|
|
61
|
+
) {
|
|
62
|
+
return RunMode.WORK;
|
|
63
|
+
}
|
|
64
|
+
return RunMode.OVERVIEW;
|
|
65
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { WorkflowRunStatus } from '../../workflow';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Branch the FE outcome strip renders for a TERMINAL run. Mirrors the
|
|
5
|
+
* branch matrix for terminal runs.
|
|
6
|
+
*
|
|
7
|
+
* Every terminal run lands in exactly one branch — the engine sets it on
|
|
8
|
+
* the {@link RunDetailDto.outcome} field at run-end so the FE never has
|
|
9
|
+
* to re-derive from `status` + reason text.
|
|
10
|
+
*/
|
|
11
|
+
export enum RunOutcomeBranch {
|
|
12
|
+
SUCCEEDED = 'succeeded',
|
|
13
|
+
FAILED = 'failed',
|
|
14
|
+
CANCELLED = 'cancelled',
|
|
15
|
+
ESCALATION_EXHAUSTED = 'escalation_exhausted',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* What the user sees in the outcome card. Each branch's `actions` list
|
|
20
|
+
* is computed server-side (only valid CTAs leak into the wire) so the
|
|
21
|
+
* FE renders without policy logic.
|
|
22
|
+
*
|
|
23
|
+
* `summary` is one human sentence — never JSON. The FE displays it
|
|
24
|
+
* verbatim under the status header.
|
|
25
|
+
*/
|
|
26
|
+
export interface RunOutcomeDto {
|
|
27
|
+
readonly branch: RunOutcomeBranch;
|
|
28
|
+
readonly summary: string;
|
|
29
|
+
readonly actions: readonly RunOutcomeActionDto[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A button rendered under the outcome card. Closed CTA enum — the FE
|
|
34
|
+
* fails-fast on unknown values rather than rendering a placeholder.
|
|
35
|
+
*/
|
|
36
|
+
export interface RunOutcomeActionDto {
|
|
37
|
+
readonly kind: RunOutcomeActionKind;
|
|
38
|
+
readonly label: string;
|
|
39
|
+
/** Optional href; if absent the action is dispatched to the engine via SDK. */
|
|
40
|
+
readonly href?: string;
|
|
41
|
+
/** Optional opaque payload the engine echoes on dispatch. */
|
|
42
|
+
readonly payload?: Readonly<Record<string, unknown>>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export enum RunOutcomeActionKind {
|
|
46
|
+
RUN_AGAIN = 'run_again',
|
|
47
|
+
OPEN_IN_KB = 'open_in_kb',
|
|
48
|
+
REDO_FROM_PHASE = 'redo_from_phase',
|
|
49
|
+
OPEN_LAST_SUCCESSFUL_ARTIFACT = 'open_last_successful_artifact',
|
|
50
|
+
RESTART_WITH_SAME_INPUTS = 'restart_with_same_inputs',
|
|
51
|
+
ADJUST_ESCALATION_CHAIN = 'adjust_escalation_chain',
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* True iff a {@link WorkflowRunStatus} represents a terminal state that
|
|
56
|
+
* carries an outcome. Mirrors {@link TERMINAL_WORKFLOW_RUN_STATUSES} but
|
|
57
|
+
* rejects `ABORTED_FOR_MIGRATION` because that branch has no user-facing
|
|
58
|
+
* outcome card (it's an internal lifecycle event).
|
|
59
|
+
*/
|
|
60
|
+
export function isOutcomeBearingStatus(status: WorkflowRunStatus): boolean {
|
|
61
|
+
return (
|
|
62
|
+
status === WorkflowRunStatus.SUCCEEDED ||
|
|
63
|
+
status === WorkflowRunStatus.FAILED ||
|
|
64
|
+
status === WorkflowRunStatus.CANCELLED
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The phase rail shown at the top of every run-detail mode. Click any
|
|
3
|
+
* dot to filter the activity panel to that phase. The rail is a stable,
|
|
4
|
+
* compile-time-known sequence per workflow definition — the engine
|
|
5
|
+
* computes it once at run dispatch and freezes it on the run row so the
|
|
6
|
+
* FE doesn't have to re-derive from runtime activity.
|
|
7
|
+
*/
|
|
8
|
+
export interface RunPhaseRailDto {
|
|
9
|
+
readonly phases: readonly RunPhaseRailEntryDto[];
|
|
10
|
+
/**
|
|
11
|
+
* Index into `phases` of the currently-active phase. `null` when the
|
|
12
|
+
* run is terminal AND the last phase already completed (the rail
|
|
13
|
+
* renders fully filled).
|
|
14
|
+
*/
|
|
15
|
+
readonly currentIndex: number | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface RunPhaseRailEntryDto {
|
|
19
|
+
readonly stageKey: string;
|
|
20
|
+
/** Display label — e.g. "Implementation", "Architecture review". */
|
|
21
|
+
readonly label: string;
|
|
22
|
+
readonly status: RunPhaseRailStatus;
|
|
23
|
+
/** ISO-8601 when the phase entered RUNNING. `null` if not yet started. */
|
|
24
|
+
readonly startedAt: string | null;
|
|
25
|
+
/** ISO-8601 when the phase reached a terminal state. `null` if still in flight. */
|
|
26
|
+
readonly endedAt: string | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Closed phase-rail status set. Distinct from {@link JobRunStatus} — a
|
|
31
|
+
* single phase rolls up many jobs and adds the WAITING / SKIPPED rail
|
|
32
|
+
* affordances the user sees. Maps from job status server-side; FE
|
|
33
|
+
* renders the icon+color directly off this enum.
|
|
34
|
+
*/
|
|
35
|
+
export enum RunPhaseRailStatus {
|
|
36
|
+
PENDING = 'pending',
|
|
37
|
+
RUNNING = 'running',
|
|
38
|
+
WAITING = 'waiting',
|
|
39
|
+
SUCCEEDED = 'succeeded',
|
|
40
|
+
FAILED = 'failed',
|
|
41
|
+
CANCELLED = 'cancelled',
|
|
42
|
+
SKIPPED = 'skipped',
|
|
43
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Three deliberately distinct revision UIs.
|
|
3
|
+
* The engine emits exactly one of these on the run detail payload; the
|
|
4
|
+
* FE renders the matching component (or nothing for `NONE`).
|
|
5
|
+
*
|
|
6
|
+
* - `REVISE` — purple checkpoint card embedded in the doc flow.
|
|
7
|
+
* CTAs: "Approve & continue" | "Revise with feedback".
|
|
8
|
+
* - `MANUAL_OVERRIDE` — warning banner shown after auto-retries are
|
|
9
|
+
* exhausted. CTAs: "Extend retries" | "Force approve" | "Reject run".
|
|
10
|
+
* - `REDO_FROM_PHASE` — terminal-only dropdown that re-dispatches from
|
|
11
|
+
* a chosen earlier phase.
|
|
12
|
+
* - `NONE` — no revision UI; the run is progressing normally.
|
|
13
|
+
*
|
|
14
|
+
* Auto-retry status surfaces as a status pill in the run header, NOT
|
|
15
|
+
* its own revision-state value. Don't extend this enum to cover
|
|
16
|
+
* non-revision states.
|
|
17
|
+
*/
|
|
18
|
+
export enum RunRevisionState {
|
|
19
|
+
NONE = 'none',
|
|
20
|
+
REVISE = 'revise',
|
|
21
|
+
MANUAL_OVERRIDE = 'manual_override',
|
|
22
|
+
REDO_FROM_PHASE = 'redo_from_phase',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Per-state payload the FE needs to render the matching component. The
|
|
27
|
+
* engine includes whichever variant matches `state` and omits the rest.
|
|
28
|
+
*
|
|
29
|
+
* `NONE` carries no payload — the discriminator alone suffices.
|
|
30
|
+
*/
|
|
31
|
+
export type RunRevisionPayload =
|
|
32
|
+
| { readonly state: RunRevisionState.NONE }
|
|
33
|
+
| { readonly state: RunRevisionState.REVISE; readonly checkpoint: RunReviseCheckpointDto }
|
|
34
|
+
| {
|
|
35
|
+
readonly state: RunRevisionState.MANUAL_OVERRIDE;
|
|
36
|
+
readonly override: RunManualOverrideDto;
|
|
37
|
+
}
|
|
38
|
+
| {
|
|
39
|
+
readonly state: RunRevisionState.REDO_FROM_PHASE;
|
|
40
|
+
readonly redo: RunRedoFromPhaseDto;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Payload for the purple checkpoint card. */
|
|
44
|
+
export interface RunReviseCheckpointDto {
|
|
45
|
+
readonly checkpointId: string;
|
|
46
|
+
readonly stageKey: string;
|
|
47
|
+
readonly title: string;
|
|
48
|
+
/** Human-readable summary of what's being reviewed. */
|
|
49
|
+
readonly summary: string;
|
|
50
|
+
/** Reviewer findings to render inline alongside the canvas. */
|
|
51
|
+
readonly findings: readonly RunReviseFindingDto[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface RunReviseFindingDto {
|
|
55
|
+
readonly id: string;
|
|
56
|
+
readonly severity: 'info' | 'minor' | 'major' | 'critical';
|
|
57
|
+
/** Optional file:line anchor — `null` if the finding is run-level. */
|
|
58
|
+
readonly anchor: string | null;
|
|
59
|
+
readonly message: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Payload for the manual-override banner (auto-retries exhausted). */
|
|
63
|
+
export interface RunManualOverrideDto {
|
|
64
|
+
readonly retryCount: number;
|
|
65
|
+
readonly retryLimit: number;
|
|
66
|
+
readonly lastFailureReason: string;
|
|
67
|
+
/** When the engine is willing to attempt one more retry on demand. */
|
|
68
|
+
readonly canExtendRetries: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Payload for the redo-from-phase dropdown (terminal runs only). */
|
|
72
|
+
export interface RunRedoFromPhaseDto {
|
|
73
|
+
readonly availablePhases: readonly RunRedoPhaseChoiceDto[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface RunRedoPhaseChoiceDto {
|
|
77
|
+
readonly stageKey: string;
|
|
78
|
+
readonly label: string;
|
|
79
|
+
/** Whether the engine has the inputs needed to re-dispatch from here. */
|
|
80
|
+
readonly available: boolean;
|
|
81
|
+
/** Human-readable reason when `available === false`. */
|
|
82
|
+
readonly unavailableReason?: string;
|
|
83
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for "the run is waiting on X" math used by the
|
|
3
|
+
* RunHeaderTimebar, the run-list "what's blocking" inline text, and the
|
|
4
|
+
* Overview blocking callout.
|
|
5
|
+
*
|
|
6
|
+
* Three different surfaces used to compute these windows independently
|
|
7
|
+
* and could disagree by the
|
|
8
|
+
* second. They now share this util.
|
|
9
|
+
*
|
|
10
|
+
* All times are ISO-8601 strings on the wire; the helpers parse + return
|
|
11
|
+
* primitive ms numbers so callers stay framework-agnostic.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export interface WaitWindow {
|
|
15
|
+
/** Inclusive start ISO-8601. */
|
|
16
|
+
readonly startedAt: string;
|
|
17
|
+
/** Exclusive end ISO-8601, or `null` if the wait is still open. */
|
|
18
|
+
readonly endedAt: string | null;
|
|
19
|
+
readonly reason: WaitReason;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Closed reason set. The user-visible label is rendered by the FE from
|
|
24
|
+
* this discriminator — never accept a free-form `reason` string from
|
|
25
|
+
* the wire.
|
|
26
|
+
*/
|
|
27
|
+
export enum WaitReason {
|
|
28
|
+
WAITING_ON_HUMAN = 'waiting_on_human',
|
|
29
|
+
WAITING_ON_AGENT = 'waiting_on_agent',
|
|
30
|
+
WAITING_ON_EXTERNAL = 'waiting_on_external',
|
|
31
|
+
WAITING_ON_DEPENDENCY = 'waiting_on_dependency',
|
|
32
|
+
WAITING_ON_QUOTA = 'waiting_on_quota',
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Total wait duration (ms) summed across windows. Open windows count
|
|
37
|
+
* up to `nowMs`. Caller passes `nowMs` (test-injectable) — the lib
|
|
38
|
+
* never reads `Date.now()` on its own.
|
|
39
|
+
*/
|
|
40
|
+
export function totalWaitMs(windows: readonly WaitWindow[], nowMs: number): number {
|
|
41
|
+
let total = 0;
|
|
42
|
+
for (const w of windows) {
|
|
43
|
+
const start = Date.parse(w.startedAt);
|
|
44
|
+
const end = w.endedAt !== null ? Date.parse(w.endedAt) : nowMs;
|
|
45
|
+
if (Number.isFinite(start) && Number.isFinite(end) && end > start) {
|
|
46
|
+
total += end - start;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return total;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The currently-open wait window, if any. Returns the most recent open
|
|
54
|
+
* window (there should never be more than one open at a time, but if
|
|
55
|
+
* the engine emits overlapping waits we surface the most recent so the
|
|
56
|
+
* FE doesn't pick a stale reason).
|
|
57
|
+
*/
|
|
58
|
+
export function currentOpenWait(windows: readonly WaitWindow[]): WaitWindow | null {
|
|
59
|
+
let bestStart = -Infinity;
|
|
60
|
+
let bestWindow: WaitWindow | null = null;
|
|
61
|
+
for (const w of windows) {
|
|
62
|
+
if (w.endedAt !== null) continue;
|
|
63
|
+
const start = Date.parse(w.startedAt);
|
|
64
|
+
if (Number.isFinite(start) && start > bestStart) {
|
|
65
|
+
bestStart = start;
|
|
66
|
+
bestWindow = w;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return bestWindow;
|
|
70
|
+
}
|