@voyantjs/workflows-orchestrator 0.38.0 → 0.38.1

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/index.d.ts CHANGED
@@ -7,6 +7,7 @@ export { createHttpStepHandler, type HttpStepHandlerDeps, type HttpStepTarget, }
7
7
  export { createInMemoryRunStore } from "./in-memory-store.js";
8
8
  export { emptyJournal } from "./journal-helpers.js";
9
9
  export { type CancelArgs, cancel, type OrchestratorDeps, type ResumeArgs, type ResumeDueAlarmsArgs, resume, resumeDueAlarms, type TriggerArgs, trigger, } from "./orchestrator.js";
10
+ export { type BuildResumeJournalInput, type BuildResumeJournalResult, type BuildSeededResumeJournalInput, buildResumeJournal, buildSeededResumeJournal, } from "./resume-run.js";
10
11
  export { type CronSpec, computeNextFire, createScheduler, manifestScheduleSources, nextCronFire, parseCron, type SchedulableDeclaration, type SchedulerDeps, type SchedulerHandle, type ScheduleSource, toMs, } from "./schedule.js";
11
12
  export * from "./types.js";
12
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,kBAAkB,GACnB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACL,KAAK,mBAAmB,EACxB,qCAAqC,EACrC,KAAK,+BAA+B,EACpC,KAAK,wBAAwB,EAC7B,qBAAqB,EACrB,gCAAgC,GACjC,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,uBAAuB,EAAE,KAAK,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AACzF,OAAO,EACL,oBAAoB,EACpB,KAAK,qBAAqB,GAC3B,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,UAAU,GACX,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACL,qBAAqB,EACrB,KAAK,mBAAmB,EACxB,KAAK,cAAc,GACpB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACnD,OAAO,EACL,KAAK,UAAU,EACf,MAAM,EACN,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,mBAAmB,EACxB,MAAM,EACN,eAAe,EACf,KAAK,WAAW,EAChB,OAAO,GACR,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACL,KAAK,QAAQ,EACb,eAAe,EACf,eAAe,EACf,uBAAuB,EACvB,YAAY,EACZ,SAAS,EACT,KAAK,sBAAsB,EAC3B,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,IAAI,GACL,MAAM,eAAe,CAAA;AACtB,cAAc,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,kBAAkB,GACnB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EACL,KAAK,mBAAmB,EACxB,qCAAqC,EACrC,KAAK,+BAA+B,EACpC,KAAK,wBAAwB,EAC7B,qBAAqB,EACrB,gCAAgC,GACjC,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,uBAAuB,EAAE,KAAK,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AACzF,OAAO,EACL,oBAAoB,EACpB,KAAK,qBAAqB,GAC3B,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,UAAU,GACX,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACL,qBAAqB,EACrB,KAAK,mBAAmB,EACxB,KAAK,cAAc,GACpB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAA;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AACnD,OAAO,EACL,KAAK,UAAU,EACf,MAAM,EACN,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,mBAAmB,EACxB,MAAM,EACN,eAAe,EACf,KAAK,WAAW,EAChB,OAAO,GACR,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACL,KAAK,uBAAuB,EAC5B,KAAK,wBAAwB,EAC7B,KAAK,6BAA6B,EAClC,kBAAkB,EAClB,wBAAwB,GACzB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,KAAK,QAAQ,EACb,eAAe,EACf,eAAe,EACf,uBAAuB,EACvB,YAAY,EACZ,SAAS,EACT,KAAK,sBAAsB,EAC3B,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,IAAI,GACL,MAAM,eAAe,CAAA;AACtB,cAAc,YAAY,CAAA"}
package/dist/index.js CHANGED
@@ -17,5 +17,6 @@ export { createHttpStepHandler, } from "./http-step-handler.js";
17
17
  export { createInMemoryRunStore } from "./in-memory-store.js";
18
18
  export { emptyJournal } from "./journal-helpers.js";
19
19
  export { cancel, resume, resumeDueAlarms, trigger, } from "./orchestrator.js";
20
+ export { buildResumeJournal, buildSeededResumeJournal, } from "./resume-run.js";
20
21
  export { computeNextFire, createScheduler, manifestScheduleSources, nextCronFire, parseCron, toMs, } from "./schedule.js";
21
22
  export * from "./types.js";
@@ -38,6 +38,13 @@ export interface TriggerArgs {
38
38
  * run.
39
39
  */
40
40
  initialJournal?: JournalSlice;
41
+ /**
42
+ * Metadata cursor paired with `initialJournal`. Resume callers that
43
+ * seed a journal with an existing `metadataState` must also seed the
44
+ * positional cursor so replayed metadata mutations are not applied
45
+ * twice.
46
+ */
47
+ initialMetadataAppliedCount?: number;
41
48
  /**
42
49
  * Compute-time budget in ms, typically from `WorkflowConfig.timeout`.
43
50
  * Parked time on waitpoints does not count against this. When the
@@ -1 +1 @@
1
- {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../src/orchestrator.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAGnD,OAAO,EAA2B,KAAK,YAAY,EAAoB,MAAM,YAAY,CAAA;AAEzF,OAAO,KAAK,EACV,YAAY,EACZ,SAAS,EACT,cAAc,EACd,UAAU,EACV,WAAW,EACX,kBAAkB,EACnB,MAAM,YAAY,CAAA;AAEnB,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,MAAM,CAAA;IACvB,KAAK,EAAE,OAAO,CAAA;IACd,UAAU,EAAE,SAAS,CAAC,YAAY,CAAC,CAAA;IACnC,WAAW,CAAC,EAAE,SAAS,CAAC,aAAa,CAAC,CAAA;IACtC,WAAW,CAAC,EAAE,UAAU,CAAA;IACxB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;;;;;;;OAUG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;OAIG;IACH,KAAK,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAA;IACvB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,cAAc,CAAC,EAAE,YAAY,CAAA;IAC7B;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAA;IAC/C;;;OAGG;IACH,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAA;CACjD;AAED,MAAM,WAAW,gBAAiB,SAAQ,YAAY;IACpD,KAAK,EAAE,cAAc,CAAA;IACrB,OAAO,EAAE,WAAW,CAAA;IACpB,gDAAgD;IAChD,WAAW,CAAC,EAAE,MAAM,MAAM,CAAA;CAC3B;AAED,wBAAsB,OAAO,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,CAAC,CA6F3F;AA8FD,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,kBAAkB,CAAA;CAC9B;AAED,wBAAsB,MAAM,CAC1B,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,gBAAgB,GACrB,OAAO,CACN;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,SAAS,CAAA;CAAE,GAC/B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,WAAW,GAAG,YAAY,GAAG,UAAU,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAClF,CA0CA;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAA;CACd;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,mBAAmB,EACzB,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAsC3B;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,wBAAsB,MAAM,CAC1B,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,gBAAgB,GACrB,OAAO,CACN;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,SAAS,CAAA;CAAE,GAC/B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,WAAW,GAAG,kBAAkB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAC3E,CAqCA"}
1
+ {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../src/orchestrator.ts"],"names":[],"mappings":"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"}
@@ -32,7 +32,7 @@ export async function trigger(args, deps) {
32
32
  input: args.input,
33
33
  journal: args.initialJournal ? cloneJournal(args.initialJournal) : emptyJournal(),
34
34
  invocationCount: 0,
35
- metadataAppliedCount: 0,
35
+ metadataAppliedCount: args.initialMetadataAppliedCount ?? 0,
36
36
  computeTimeMs: 0,
37
37
  timeoutMs: args.timeoutMs,
38
38
  priority: args.priority,
@@ -0,0 +1,23 @@
1
+ import type { JournalSlice, RunRecord } from "./types.js";
2
+ export interface BuildResumeJournalInput {
3
+ parent: RunRecord;
4
+ resumeFromStep?: string;
5
+ seedResults?: Record<string, unknown>;
6
+ now?: () => number;
7
+ }
8
+ export interface BuildResumeJournalResult {
9
+ resumeFromStep: string;
10
+ journal: JournalSlice;
11
+ metadataAppliedCount: number;
12
+ }
13
+ export interface BuildSeededResumeJournalInput {
14
+ parentRunId: string;
15
+ resumeFromStep: string;
16
+ seedResults: Record<string, unknown>;
17
+ metadataState?: Record<string, unknown>;
18
+ metadataAppliedCount?: number;
19
+ now?: () => number;
20
+ }
21
+ export declare function buildResumeJournal(input: BuildResumeJournalInput): BuildResumeJournalResult;
22
+ export declare function buildSeededResumeJournal(input: BuildSeededResumeJournalInput): BuildResumeJournalResult;
23
+ //# sourceMappingURL=resume-run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resume-run.d.ts","sourceRoot":"","sources":["../src/resume-run.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAoB,MAAM,YAAY,CAAA;AAE3E,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,SAAS,CAAA;IACjB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACrC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,wBAAwB;IACvC,cAAc,EAAE,MAAM,CAAA;IACtB,OAAO,EAAE,YAAY,CAAA;IACrB,oBAAoB,EAAE,MAAM,CAAA;CAC7B;AAED,MAAM,WAAW,6BAA6B;IAC5C,WAAW,EAAE,MAAM,CAAA;IACnB,cAAc,EAAE,MAAM,CAAA;IACtB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACpC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACvC,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,wBAAwB,CAwC3F;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,6BAA6B,GACnC,wBAAwB,CAgB1B"}
@@ -0,0 +1,65 @@
1
+ import { emptyJournal } from "./journal-helpers.js";
2
+ export function buildResumeJournal(input) {
3
+ const resumeFromStep = input.resumeFromStep ?? findFirstFailedStep(input.parent);
4
+ if (!resumeFromStep) {
5
+ throw new Error(`run "${input.parent.id}" has no failed step; pass resumeFromStep explicitly to resume it`);
6
+ }
7
+ const journal = emptyJournal();
8
+ journal.metadataState = structuredClone(input.parent.journal.metadataState);
9
+ if (input.seedResults) {
10
+ return buildSeededResumeJournal({
11
+ parentRunId: input.parent.id,
12
+ resumeFromStep,
13
+ seedResults: input.seedResults,
14
+ metadataState: journal.metadataState,
15
+ metadataAppliedCount: input.parent.metadataAppliedCount,
16
+ now: input.now,
17
+ });
18
+ }
19
+ for (const [stepId, entry] of Object.entries(input.parent.journal.stepResults)) {
20
+ if (stepId === resumeFromStep)
21
+ break;
22
+ if (entry.status !== "ok") {
23
+ throw new Error(`step "${stepId}" completed before "${resumeFromStep}" but is not successful; cannot seed resume journal`);
24
+ }
25
+ journal.stepResults[stepId] = structuredClone(entry);
26
+ }
27
+ return {
28
+ resumeFromStep,
29
+ journal,
30
+ metadataAppliedCount: input.parent.metadataAppliedCount,
31
+ };
32
+ }
33
+ export function buildSeededResumeJournal(input) {
34
+ const journal = emptyJournal();
35
+ journal.metadataState = input.metadataState
36
+ ? structuredClone(input.metadataState)
37
+ : {};
38
+ const now = input.now ?? (() => Date.now());
39
+ let at = now();
40
+ for (const [stepId, output] of Object.entries(input.seedResults)) {
41
+ journal.stepResults[stepId] = seededStepEntry(output, at);
42
+ at += 1;
43
+ }
44
+ return {
45
+ resumeFromStep: input.resumeFromStep,
46
+ journal,
47
+ metadataAppliedCount: input.metadataAppliedCount ?? 0,
48
+ };
49
+ }
50
+ function findFirstFailedStep(parent) {
51
+ for (const [stepId, entry] of Object.entries(parent.journal.stepResults)) {
52
+ if (entry.status === "err")
53
+ return stepId;
54
+ }
55
+ return undefined;
56
+ }
57
+ function seededStepEntry(output, at) {
58
+ return {
59
+ attempt: 1,
60
+ status: "ok",
61
+ output,
62
+ startedAt: at,
63
+ finishedAt: at,
64
+ };
65
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voyantjs/workflows-orchestrator",
3
- "version": "0.38.0",
3
+ "version": "0.38.1",
4
4
  "description": "Reference orchestrator core for Voyant Workflows — drives runs through the tenant step handler over the v1 wire protocol.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -30,7 +30,7 @@
30
30
  "NOTICE"
31
31
  ],
32
32
  "dependencies": {
33
- "@voyantjs/workflows": "0.38.0"
33
+ "@voyantjs/workflows": "0.38.1"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^20.12.0",
package/src/index.ts CHANGED
@@ -50,6 +50,13 @@ export {
50
50
  type TriggerArgs,
51
51
  trigger,
52
52
  } from "./orchestrator.js"
53
+ export {
54
+ type BuildResumeJournalInput,
55
+ type BuildResumeJournalResult,
56
+ type BuildSeededResumeJournalInput,
57
+ buildResumeJournal,
58
+ buildSeededResumeJournal,
59
+ } from "./resume-run.js"
53
60
  export {
54
61
  type CronSpec,
55
62
  computeNextFire,
@@ -58,6 +58,13 @@ export interface TriggerArgs {
58
58
  * run.
59
59
  */
60
60
  initialJournal?: JournalSlice
61
+ /**
62
+ * Metadata cursor paired with `initialJournal`. Resume callers that
63
+ * seed a journal with an existing `metadataState` must also seed the
64
+ * positional cursor so replayed metadata mutations are not applied
65
+ * twice.
66
+ */
67
+ initialMetadataAppliedCount?: number
61
68
  /**
62
69
  * Compute-time budget in ms, typically from `WorkflowConfig.timeout`.
63
70
  * Parked time on waitpoints does not count against this. When the
@@ -109,7 +116,7 @@ export async function trigger(args: TriggerArgs, deps: OrchestratorDeps): Promis
109
116
  input: args.input,
110
117
  journal: args.initialJournal ? cloneJournal(args.initialJournal) : emptyJournal(),
111
118
  invocationCount: 0,
112
- metadataAppliedCount: 0,
119
+ metadataAppliedCount: args.initialMetadataAppliedCount ?? 0,
113
120
  computeTimeMs: 0,
114
121
  timeoutMs: args.timeoutMs,
115
122
  priority: args.priority,
@@ -0,0 +1,103 @@
1
+ import { emptyJournal } from "./journal-helpers.js"
2
+ import type { JournalSlice, RunRecord, StepJournalEntry } from "./types.js"
3
+
4
+ export interface BuildResumeJournalInput {
5
+ parent: RunRecord
6
+ resumeFromStep?: string
7
+ seedResults?: Record<string, unknown>
8
+ now?: () => number
9
+ }
10
+
11
+ export interface BuildResumeJournalResult {
12
+ resumeFromStep: string
13
+ journal: JournalSlice
14
+ metadataAppliedCount: number
15
+ }
16
+
17
+ export interface BuildSeededResumeJournalInput {
18
+ parentRunId: string
19
+ resumeFromStep: string
20
+ seedResults: Record<string, unknown>
21
+ metadataState?: Record<string, unknown>
22
+ metadataAppliedCount?: number
23
+ now?: () => number
24
+ }
25
+
26
+ export function buildResumeJournal(input: BuildResumeJournalInput): BuildResumeJournalResult {
27
+ const resumeFromStep = input.resumeFromStep ?? findFirstFailedStep(input.parent)
28
+ if (!resumeFromStep) {
29
+ throw new Error(
30
+ `run "${input.parent.id}" has no failed step; pass resumeFromStep explicitly to resume it`,
31
+ )
32
+ }
33
+
34
+ const journal = emptyJournal()
35
+ journal.metadataState = structuredClone(input.parent.journal.metadataState) as Record<
36
+ string,
37
+ unknown
38
+ >
39
+
40
+ if (input.seedResults) {
41
+ return buildSeededResumeJournal({
42
+ parentRunId: input.parent.id,
43
+ resumeFromStep,
44
+ seedResults: input.seedResults,
45
+ metadataState: journal.metadataState,
46
+ metadataAppliedCount: input.parent.metadataAppliedCount,
47
+ now: input.now,
48
+ })
49
+ }
50
+
51
+ for (const [stepId, entry] of Object.entries(input.parent.journal.stepResults)) {
52
+ if (stepId === resumeFromStep) break
53
+ if (entry.status !== "ok") {
54
+ throw new Error(
55
+ `step "${stepId}" completed before "${resumeFromStep}" but is not successful; cannot seed resume journal`,
56
+ )
57
+ }
58
+ journal.stepResults[stepId] = structuredClone(entry) as StepJournalEntry
59
+ }
60
+
61
+ return {
62
+ resumeFromStep,
63
+ journal,
64
+ metadataAppliedCount: input.parent.metadataAppliedCount,
65
+ }
66
+ }
67
+
68
+ export function buildSeededResumeJournal(
69
+ input: BuildSeededResumeJournalInput,
70
+ ): BuildResumeJournalResult {
71
+ const journal = emptyJournal()
72
+ journal.metadataState = input.metadataState
73
+ ? (structuredClone(input.metadataState) as Record<string, unknown>)
74
+ : {}
75
+ const now = input.now ?? (() => Date.now())
76
+ let at = now()
77
+ for (const [stepId, output] of Object.entries(input.seedResults)) {
78
+ journal.stepResults[stepId] = seededStepEntry(output, at)
79
+ at += 1
80
+ }
81
+ return {
82
+ resumeFromStep: input.resumeFromStep,
83
+ journal,
84
+ metadataAppliedCount: input.metadataAppliedCount ?? 0,
85
+ }
86
+ }
87
+
88
+ function findFirstFailedStep(parent: RunRecord): string | undefined {
89
+ for (const [stepId, entry] of Object.entries(parent.journal.stepResults)) {
90
+ if (entry.status === "err") return stepId
91
+ }
92
+ return undefined
93
+ }
94
+
95
+ function seededStepEntry(output: unknown, at: number): StepJournalEntry {
96
+ return {
97
+ attempt: 1,
98
+ status: "ok",
99
+ output,
100
+ startedAt: at,
101
+ finishedAt: at,
102
+ }
103
+ }