| author | |
| committer | |
| log | 736130c2fd39b85cd515fa2a41cd10c8a8a52630 |
| tree | 8711cb01b83d3a765cc77d830c7d8e317ab7fdd0 |
| parent | 9333509c918083cbfb7e66759ddbcbfa71a3a452 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>9 files changed, 245 insertions(+), 11 deletions(-)
apps/server/src/orchestration-v2/Orchestrator.ts+39| ... | ... | @@ -39,6 +39,7 @@ import { |
| 39 | 39 | type OrchestrationV2Subagent, |
| 40 | 40 | type OrchestrationV2ThreadProjection, |
| 41 | 41 | type OrchestrationV2TurnItem, |
| 42 | orchestrationV2RunWorkStartedAt, | |
| 42 | 43 | ProviderInstanceId, |
| 43 | 44 | type ProviderSessionId, |
| 44 | 45 | RunId, |
| ... | ... | @@ -304,6 +305,37 @@ function nextRunOrdinal(projection: Pick<OrchestrationV2ThreadProjection, "runs" |
| 304 | 305 | return projection.runs.length + 1; |
| 305 | 306 | } |
| 306 | 307 | |
| 308 | /** | |
| 309 | * A wake (background notification, delegated task result, restart | |
| 310 | * continuation) carries on the work of the run that started last, so it keeps | |
| 311 | * that work's start. Stamp it when the wake run starts, not when it queues: | |
| 312 | * a queued prompt ahead of it has no start yet, and delegated results jump | |
| 313 | * the queue. Other runs start new work. | |
| 314 | */ | |
| 315 | function wakeWorkStartedAt( | |
| 316 | runs: ReadonlyArray<OrchestrationV2Run>, | |
| 317 | trigger: { | |
| 318 | readonly notification?: unknown; | |
| 319 | readonly delegatedCompletion?: unknown; | |
| 320 | readonly restartContinuationOfRunId?: RunId | undefined; | |
| 321 | }, | |
| 322 | ): Pick<OrchestrationV2Run, "workStartedAt"> { | |
| 323 | if ( | |
| 324 | trigger.notification === undefined && | |
| 325 | trigger.delegatedCompletion === undefined && | |
| 326 | trigger.restartContinuationOfRunId === undefined | |
| 327 | ) { | |
| 328 | return {}; | |
| 329 | } | |
| 330 | const previous = runs | |
| 331 | .flatMap((run) => (run.startedAt === null ? [] : [{ run, startedAt: run.startedAt }])) | |
| 332 | .toSorted( | |
| 333 | (left, right) => | |
| 334 | DateTime.Order(right.startedAt, left.startedAt) || right.run.ordinal - left.run.ordinal, | |
| 335 | )[0]?.run; | |
| 336 | return previous === undefined ? {} : { workStartedAt: orchestrationV2RunWorkStartedAt(previous) }; | |
| 337 | } | |
| 338 | ||
| 307 | 339 | function isNativeMaintenanceCommand(message: { |
| 308 | 340 | readonly text: string; |
| 309 | 341 | readonly attachments: ReadonlyArray<ChatAttachment>; |
| ... | ... | @@ -1459,6 +1491,11 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio |
| 1459 | 1491 | queuePosition: null, |
| 1460 | 1492 | startedAt: null, |
| 1461 | 1493 | contextHandoffId: activeHandoff?.id ?? null, |
| 1494 | ...wakeWorkStartedAt(projection.runs, { | |
| 1495 | notification: queuedMessage.notification, | |
| 1496 | delegatedCompletion: queuedMessage.delegatedCompletion, | |
| 1497 | restartContinuationOfRunId: queuedRun.restartContinuationOfRunId, | |
| 1498 | }), | |
| 1462 | 1499 | }; |
| 1463 | 1500 | const userTurnItem: OrchestrationV2TurnItem = { |
| 1464 | 1501 | ...(legacyQueuedTurnItem ?? { |
| ... | ... | @@ -4949,6 +4986,7 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio |
| 4949 | 4986 | ...(command.restartContinuationOfRunId === undefined |
| 4950 | 4987 | ? {} |
| 4951 | 4988 | : { restartContinuationOfRunId: command.restartContinuationOfRunId }), |
| 4989 | ...wakeWorkStartedAt(projection.runs, command), | |
| 4952 | 4990 | }; |
| 4953 | 4991 | const attempt: OrchestrationV2RunAttempt = { |
| 4954 | 4992 | id: attemptId, |
| ... | ... | @@ -5642,6 +5680,7 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio |
| 5642 | 5680 | ...(command.restartContinuationOfRunId === undefined |
| 5643 | 5681 | ? {} |
| 5644 | 5682 | : { restartContinuationOfRunId: command.restartContinuationOfRunId }), |
| 5683 | ...wakeWorkStartedAt(projection.runs, command), | |
| 5645 | 5684 | }; |
| 5646 | 5685 | const attempt: OrchestrationV2RunAttempt = { |
| 5647 | 5686 | id: attemptId, |
apps/server/src/orchestration-v2/ProjectionStore.test.ts+31| ... | ... | @@ -1982,6 +1982,37 @@ it.layer(TestLayer)("ProjectionStoreV2", (it) => { |
| 1982 | 1982 | assert.isNull(onlyHeldShell.latestRunId); |
| 1983 | 1983 | assert.equal(onlyHeldShell.status, "idle"); |
| 1984 | 1984 | } |
| 1985 | ||
| 1986 | // A wake run counts from the start of the work it continues. | |
| 1987 | yield* projectionStore.apply({ | |
| 1988 | id: EventId.make("event:projection-shell-interruptible:wake"), | |
| 1989 | type: "run.updated", | |
| 1990 | threadId, | |
| 1991 | runId, | |
| 1992 | nodeId: rootNodeId, | |
| 1993 | driver, | |
| 1994 | occurredAt: later, | |
| 1995 | payload: { | |
| 1996 | ...run, | |
| 1997 | status: "running", | |
| 1998 | requestedAt: later, | |
| 1999 | startedAt: later, | |
| 2000 | workStartedAt: now, | |
| 2001 | }, | |
| 2002 | }); | |
| 2003 | const wakeProjection = yield* projectionStore.getThreadProjection(threadId); | |
| 2004 | const wakeSqlShell = (yield* projectionStore.getShellSnapshot()).threads.find( | |
| 2005 | (row) => row.id === threadId, | |
| 2006 | )!; | |
| 2007 | for (const wakeShell of [ | |
| 2008 | wakeSqlShell, | |
| 2009 | ProjectionStore.threadShellFromProjection(wakeProjection), | |
| 2010 | ]) { | |
| 2011 | assert.equal( | |
| 2012 | wakeShell.activityRunStartedAt && DateTime.toEpochMillis(wakeShell.activityRunStartedAt), | |
| 2013 | DateTime.toEpochMillis(now), | |
| 2014 | ); | |
| 2015 | } | |
| 1985 | 2016 | }), |
| 1986 | 2017 | ); |
| 1987 | 2018 |
apps/server/src/orchestration-v2/ProjectionStore.ts+9-2| ... | ... | @@ -48,6 +48,7 @@ import { |
| 48 | 48 | OrchestrationV2RuntimeRequestJson as OrchestrationV2RuntimeRequestJsonSchema, |
| 49 | 49 | OrchestrationV2SubagentJson as OrchestrationV2SubagentJsonSchema, |
| 50 | 50 | OrchestrationV2TurnItemJson as OrchestrationV2TurnItemJsonSchema, |
| 51 | orchestrationV2RunWorkStartedAt, | |
| 51 | 52 | RunId, |
| 52 | 53 | CheckpointScopeId, |
| 53 | 54 | ThreadId, |
| ... | ... | @@ -1368,7 +1369,8 @@ export function threadShellFromProjection( |
| 1368 | 1369 | latestRunCompletedAt: latestRun?.completedAt ?? null, |
| 1369 | 1370 | activeRunId: activeRun?.id ?? null, |
| 1370 | 1371 | activityRunStatus: activityRun?.status ?? null, |
| 1371 | activityRunStartedAt: activityRun?.startedAt ?? activityRun?.requestedAt ?? null, | |
| 1372 | activityRunStartedAt: | |
| 1373 | activityRun === null ? null : orchestrationV2RunWorkStartedAt(activityRun), | |
| 1372 | 1374 | status: latestRun?.status ?? "idle", |
| 1373 | 1375 | ...threadErrorSummary( |
| 1374 | 1376 | latestRootProviderFailure(latestRun, projection.turnItems), |
| ... | ... | @@ -4782,7 +4784,12 @@ export const layer: Layer.Layer<ProjectionStoreV2, never, SqlClient.SqlClient> = |
| 4782 | 4784 | LIMIT 1 |
| 4783 | 4785 | ) AS activity_run_status, |
| 4784 | 4786 | ( |
| 4785 | SELECT COALESCE(json_extract(r.payload_json, '$.startedAt'), r.requested_at) | |
| 4787 | -- Mirrors orchestrationV2RunWorkStartedAt. | |
| 4788 | SELECT COALESCE( | |
| 4789 | json_extract(r.payload_json, '$.workStartedAt'), | |
| 4790 | json_extract(r.payload_json, '$.startedAt'), | |
| 4791 | r.requested_at | |
| 4792 | ) | |
| 4786 | 4793 | FROM orchestration_v2_projection_runs r |
| 4787 | 4794 | WHERE r.thread_id = t.thread_id |
| 4788 | 4795 | AND r.status IN ('preparing', 'starting', 'running', 'waiting') |
apps/server/src/orchestration-v2/runtimeLayer.test.ts+118| ... | ... | @@ -2800,6 +2800,124 @@ it.layer(TestLayer)("OrchestrationV2LayerLive lifecycle", (it) => { |
| 2800 | 2800 | }), |
| 2801 | 2801 | ); |
| 2802 | 2802 | |
| 2803 | it.effect("starts a wake's work clock from the run that ran before it", () => | |
| 2804 | Effect.gen(function* () { | |
| 2805 | const orchestrator = yield* Orchestrator.OrchestratorV2; | |
| 2806 | const eventSink = yield* EventSink.EventSinkV2; | |
| 2807 | const threadId = ThreadId.make("runtime-layer-wake-work-start-thread"); | |
| 2808 | const messageId = (key: string) => MessageId.make(`runtime-layer-wake-work-start-${key}`); | |
| 2809 | ||
| 2810 | yield* orchestrator.dispatch({ | |
| 2811 | type: "thread.create", | |
| 2812 | createdBy: "user", | |
| 2813 | creationSource: "web", | |
| 2814 | commandId: CommandId.make("runtime-layer-wake-work-start-create"), | |
| 2815 | threadId, | |
| 2816 | projectId: ProjectId.make("runtime-layer-wake-work-start-project"), | |
| 2817 | title: "Wake work start", | |
| 2818 | modelSelection, | |
| 2819 | runtimeMode: "full-access", | |
| 2820 | interactionMode: "default", | |
| 2821 | branch: null, | |
| 2822 | worktreePath: process.cwd(), | |
| 2823 | }); | |
| 2824 | const dispatch = (key: string, wake: boolean) => | |
| 2825 | orchestrator.dispatch({ | |
| 2826 | type: "message.dispatch", | |
| 2827 | createdBy: wake ? "agent" : "user", | |
| 2828 | creationSource: wake ? "provider" : "web", | |
| 2829 | ...(wake | |
| 2830 | ? { | |
| 2831 | notification: { | |
| 2832 | source: { kind: "background_task" as const }, | |
| 2833 | outcome: "updated" as const, | |
| 2834 | summary: "Background activity updated", | |
| 2835 | }, | |
| 2836 | } | |
| 2837 | : {}), | |
| 2838 | commandId: CommandId.make(`runtime-layer-wake-work-start-${key}`), | |
| 2839 | threadId, | |
| 2840 | messageId: messageId(key), | |
| 2841 | text: key, | |
| 2842 | attachments: [], | |
| 2843 | modelSelection, | |
| 2844 | dispatchMode: | |
| 2845 | key === "prompt" ? { type: "start_immediately" } : { type: "queue_after_active" }, | |
| 2846 | }); | |
| 2847 | const runFor = (key: string) => | |
| 2848 | Effect.map(orchestrator.getThreadProjection(threadId), ({ runs }) => { | |
| 2849 | const run = runs.find((candidate) => candidate.userMessageId === messageId(key)); | |
| 2850 | assert.isDefined(run); | |
| 2851 | return run; | |
| 2852 | }); | |
| 2853 | yield* dispatch("prompt", false); | |
| 2854 | yield* dispatch("queued", false); | |
| 2855 | yield* dispatch("early-wake", true); | |
| 2856 | // The early wake now runs ahead of the older queued prompt, as a | |
| 2857 | // delegated result does when it jumps the queue. | |
| 2858 | yield* orchestrator.dispatch({ | |
| 2859 | type: "queued-run.reorder", | |
| 2860 | commandId: CommandId.make("runtime-layer-wake-work-start-reorder"), | |
| 2861 | threadId, | |
| 2862 | runId: (yield* runFor("queued")).id, | |
| 2863 | beforeRunId: null, | |
| 2864 | }); | |
| 2865 | yield* dispatch("late-wake", true); | |
| 2866 | // A queued wake has no clock yet: what runs before it is still unknown. | |
| 2867 | assert.isUndefined((yield* runFor("early-wake")).workStartedAt); | |
| 2868 | ||
| 2869 | const startedRunIds = yield* Queue.unbounded<RunId>(); | |
| 2870 | const afterSequence = yield* orchestrator.getThreadEventSequence(threadId); | |
| 2871 | yield* eventSink.stream({ threadId, afterSequence }).pipe( | |
| 2872 | Stream.runForEach((stored) => | |
| 2873 | stored.event.type === "run.updated" && stored.event.payload.status === "starting" | |
| 2874 | ? Queue.offer(startedRunIds, stored.event.payload.id) | |
| 2875 | : Effect.void, | |
| 2876 | ), | |
| 2877 | Effect.forkScoped, | |
| 2878 | ); | |
| 2879 | yield* Effect.yieldNow; | |
| 2880 | ||
| 2881 | const now = yield* DateTime.now; | |
| 2882 | // Runs start and settle the way the provider would report them. | |
| 2883 | const settle = (key: string, startedAt: DateTime.Utc) => | |
| 2884 | Effect.gen(function* () { | |
| 2885 | const run = yield* runFor(key); | |
| 2886 | yield* eventSink.write({ | |
| 2887 | events: [ | |
| 2888 | { | |
| 2889 | id: EventId.make(`runtime-layer-wake-work-start-${key}-completed`), | |
| 2890 | type: "run.updated", | |
| 2891 | threadId, | |
| 2892 | runId: run.id, | |
| 2893 | ...(run.rootNodeId === null ? {} : { nodeId: run.rootNodeId }), | |
| 2894 | providerInstanceId: run.providerInstanceId, | |
| 2895 | occurredAt: startedAt, | |
| 2896 | payload: { ...run, status: "completed", startedAt, completedAt: startedAt }, | |
| 2897 | }, | |
| 2898 | ], | |
| 2899 | }); | |
| 2900 | }); | |
| 2901 | const millis = (value: DateTime.Utc | undefined) => | |
| 2902 | value === undefined ? undefined : DateTime.toEpochMillis(value); | |
| 2903 | ||
| 2904 | yield* settle("prompt", now); | |
| 2905 | assert.equal(yield* Queue.take(startedRunIds), (yield* runFor("early-wake")).id); | |
| 2906 | assert.equal(millis((yield* runFor("early-wake")).workStartedAt), millis(now)); | |
| 2907 | ||
| 2908 | yield* settle("early-wake", DateTime.add(now, { seconds: 1 })); | |
| 2909 | assert.equal(yield* Queue.take(startedRunIds), (yield* runFor("queued")).id); | |
| 2910 | assert.isUndefined((yield* runFor("queued")).workStartedAt); | |
| 2911 | ||
| 2912 | // The queued prompt starts long after it was requested; the wake after it | |
| 2913 | // counts from that start, not from the request. | |
| 2914 | const queuedStartedAt = DateTime.add(now, { minutes: 10 }); | |
| 2915 | yield* settle("queued", queuedStartedAt); | |
| 2916 | assert.equal(yield* Queue.take(startedRunIds), (yield* runFor("late-wake")).id); | |
| 2917 | assert.equal(millis((yield* runFor("late-wake")).workStartedAt), millis(queuedStartedAt)); | |
| 2918 | }), | |
| 2919 | ); | |
| 2920 | ||
| 2803 | 2921 | it.effect.each(["usage_limit", "provider_error"] as const)( |
| 2804 | 2922 | "handles a queued message after a %s failure", |
| 2805 | 2923 | (failureClass) => |
apps/web/src/components/Sidebar.logic.ts+3-4| ... | ... | @@ -1147,10 +1147,9 @@ export function sortInboxThreadsByReturn< |
| 1147 | 1147 | ); |
| 1148 | 1148 | } |
| 1149 | 1149 | |
| 1150 | /** The timestamp a working thread's elapsed label counts from: the running | |
| 1151 | turn's start (request time until adoption), falling back to the session's | |
| 1152 | last transition when the turn projection lags behind. Malformed | |
| 1153 | timestamps fall through to the next candidate, not just missing ones. */ | |
| 1150 | /** The timestamp a working thread's elapsed label counts from: when its | |
| 1151 | current work started (request time until adoption). Background wakes do | |
| 1152 | not reset it. Malformed timestamps fall through to the next candidate. */ | |
| 1154 | 1153 | export function resolveWorkingStartedAt( |
| 1155 | 1154 | thread: Pick<SidebarThreadSummary, "latestRun" | "runtime">, |
| 1156 | 1155 | ): string | null { |
packages/client-runtime/src/state/models.ts+4-1| ... | ... | @@ -296,7 +296,10 @@ export function resolveThreadProviderStack( |
| 296 | 296 | return [...previous.slice(-(THREAD_PROVIDER_STACK_LIMIT - 1)), current]; |
| 297 | 297 | } |
| 298 | 298 | |
| 299 | /** Both shell and detail timers use the activity-owning run, never last activity. */ | |
| 299 | /** | |
| 300 | * Both shell and detail timers count from the activity-owning run's work | |
| 301 | * start, never last activity. A wake keeps the start of the work it continues. | |
| 302 | */ | |
| 300 | 303 | export function resolveThreadWorkingStartedAt(input: { |
| 301 | 304 | readonly latestRun: Pick< |
| 302 | 305 | ThreadRunSummary, |
packages/client-runtime/src/state/threadExecution.test.ts+13| ... | ... | @@ -96,6 +96,19 @@ describe("thread execution presentation", () => { |
| 96 | 96 | ).toMatchObject({ status: "running", lastError: null, lastErrorClass: null }); |
| 97 | 97 | }); |
| 98 | 98 | |
| 99 | it("counts a wake run's activity from the start of the work it continues", () => { | |
| 100 | const workStartedAt = DateTime.makeUnsafe("2026-07-28T09:20:00.000Z"); | |
| 101 | const wake = { ...run("wake", 2, "running"), workStartedAt }; | |
| 102 | expect( | |
| 103 | deriveThreadRuntime({ ...v2Projection, runs: [run("prompt", 1, "completed"), wake] }) | |
| 104 | ?.activityStartedAt, | |
| 105 | ).toBe("2026-07-28T09:20:00.000Z"); | |
| 106 | expect( | |
| 107 | deriveThreadRuntime({ ...v2Projection, runs: [run("prompt", 1, "running")] }) | |
| 108 | ?.activityStartedAt, | |
| 109 | ).toBe("2026-07-28T10:00:00.000Z"); | |
| 110 | }); | |
| 111 | ||
| 99 | 112 | it("keeps a subscription limit visible while later messages stay queued", () => { |
| 100 | 113 | const failed = { |
| 101 | 114 | ...run("limited", 1, "failed"), |
packages/client-runtime/src/state/threadExecution.ts+7-3| ... | ... | @@ -13,6 +13,7 @@ import { |
| 13 | 13 | type ServerProviderModel, |
| 14 | 14 | type OrchestrationV2ExecutionNode, |
| 15 | 15 | type OrchestrationV2ThreadProjection, |
| 16 | orchestrationV2RunWorkStartedAt, | |
| 16 | 17 | type ThreadId, |
| 17 | 18 | } from "@t3tools/contracts"; |
| 18 | 19 | import { derivePendingBackgroundWork } from "@t3tools/shared/orchestrationV2PendingBackgroundWork"; |
| ... | ... | @@ -213,6 +214,9 @@ export function deriveThreadRuntime( |
| 213 | 214 | const usageLimitedRun = presentedUsageLimitRun(projection); |
| 214 | 215 | const latestRunProjection = presentedLatestRun(projection); |
| 215 | 216 | const activityRun = deriveThreadActivityRun(projection); |
| 217 | const liveActivityRun = latestMatchingRun(projection, (run) => | |
| 218 | ACTIVITY_RUN_STATUSES.has(run.status), | |
| 219 | ); | |
| 216 | 220 | if (latestRun === null && projection.thread.activeProviderThreadId === null) return null; |
| 217 | 221 | const activeRunId = |
| 218 | 222 | latestMatchingRun(projection, (run) => INTERRUPTIBLE_RUN_STATUSES.has(run.status))?.id ?? null; |
| ... | ... | @@ -232,9 +236,9 @@ export function deriveThreadRuntime( |
| 232 | 236 | : (activityRun?.status ?? "idle"), |
| 233 | 237 | activeRunId, |
| 234 | 238 | activityStartedAt: |
| 235 | activityRun !== null && ACTIVITY_RUN_STATUSES.has(activityRun.status) | |
| 236 | ? (activityRun.startedAt ?? activityRun.requestedAt) | |
| 237 | : null, | |
| 239 | liveActivityRun === null | |
| 240 | ? null | |
| 241 | : DateTime.formatIso(orchestrationV2RunWorkStartedAt(liveActivityRun)), | |
| 238 | 242 | providerInstanceId: projection.thread.providerInstanceId, |
| 239 | 243 | providerName: providerSession?.driver ?? null, |
| 240 | 244 | ...threadErrorSummary( |
packages/contracts/src/orchestrationV2.ts+21-1| ... | ... | @@ -528,6 +528,12 @@ export const OrchestrationV2Run = Schema.Struct({ |
| 528 | 528 | contextHandoffId: Schema.NullOr(ContextHandoffId), |
| 529 | 529 | /** Links server-generated restart continuations to the interrupted run. */ |
| 530 | 530 | restartContinuationOfRunId: Schema.optional(RunId), |
| 531 | /** | |
| 532 | * Set on wake runs (background notifications, delegated task results, | |
| 533 | * restart continuations): when the work they continue started. Read it | |
| 534 | * through orchestrationV2RunWorkStartedAt. | |
| 535 | */ | |
| 536 | workStartedAt: Schema.optional(Schema.DateTimeUtc), | |
| 531 | 537 | /** |
| 532 | 538 | * Set by restart recovery on the thread's latest started run. Delivered to |
| 533 | 539 | * the provider with the first later run that reaches a provider turn. |
| ... | ... | @@ -545,6 +551,16 @@ export const OrchestrationV2Run = Schema.Struct({ |
| 545 | 551 | }); |
| 546 | 552 | export type OrchestrationV2Run = typeof OrchestrationV2Run.Type; |
| 547 | 553 | |
| 554 | /** | |
| 555 | * When the work a run belongs to started. A wake does not start new work, so | |
| 556 | * working timers count from the prompt that did, not from the latest wake. | |
| 557 | */ | |
| 558 | export function orchestrationV2RunWorkStartedAt( | |
| 559 | run: Pick<OrchestrationV2Run, "workStartedAt" | "startedAt" | "requestedAt">, | |
| 560 | ): OrchestrationV2Run["requestedAt"] { | |
| 561 | return run.workStartedAt ?? run.startedAt ?? run.requestedAt; | |
| 562 | } | |
| 563 | ||
| 548 | 564 | export const OrchestrationV2RunAttempt = Schema.Struct({ |
| 549 | 565 | id: RunAttemptId, |
| 550 | 566 | // Provider-thread rows can be reused after recovery; retain the native input destination. |
| ... | ... | @@ -1688,7 +1704,10 @@ export const OrchestrationV2ThreadShell = Schema.Struct({ |
| 1688 | 1704 | latestRunStartedAt: Schema.optional(Schema.NullOr(Schema.DateTimeUtc)), |
| 1689 | 1705 | latestRunCompletedAt: Schema.optional(Schema.NullOr(Schema.DateTimeUtc)), |
| 1690 | 1706 | activeRunId: Schema.NullOr(RunId), |
| 1691 | /** Start of the activity-owning run; request time while it is preparing. */ | |
| 1707 | /** | |
| 1708 | * orchestrationV2RunWorkStartedAt of the activity-owning run: a wake keeps | |
| 1709 | * the start of the work it continues; request time while preparing. | |
| 1710 | */ | |
| 1692 | 1711 | activityRunStartedAt: Schema.optional(Schema.NullOr(Schema.DateTimeUtc)), |
| 1693 | 1712 | activityRunStatus: Schema.optional( |
| 1694 | 1713 | Schema.NullOr(Schema.Literals(["preparing", "starting", "running", "waiting"])), |
| ... | ... | @@ -1841,6 +1860,7 @@ export const OrchestrationV2RunJson = OrchestrationV2Run.mapFields((fields) => ( |
| 1841 | 1860 | requestedAt: Schema.DateTimeUtcFromString, |
| 1842 | 1861 | startedAt: Schema.NullOr(Schema.DateTimeUtcFromString), |
| 1843 | 1862 | completedAt: Schema.NullOr(Schema.DateTimeUtcFromString), |
| 1863 | workStartedAt: Schema.optional(Schema.DateTimeUtcFromString), | |
| 1844 | 1864 | })); |
| 1845 | 1865 | export type OrchestrationV2RunJson = typeof OrchestrationV2RunJson.Type; |
| 1846 | 1866 |