@bastani/atomic 0.9.13 → 0.9.14-alpha.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/CHANGELOG.md +12 -0
- package/dist/builtin/i-have-adhd/package.json +1 -1
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +12 -0
- package/dist/builtin/workflows/README.md +9 -0
- package/dist/builtin/workflows/builtin/adversarial-verification.ts +4 -0
- package/dist/builtin/workflows/builtin/classify-and-act.ts +4 -0
- package/dist/builtin/workflows/builtin/fan-out-and-synthesize.ts +4 -0
- package/dist/builtin/workflows/builtin/generate-and-filter.ts +4 -0
- package/dist/builtin/workflows/builtin/goal.ts +4 -0
- package/dist/builtin/workflows/builtin/loop-until-done.ts +4 -0
- package/dist/builtin/workflows/builtin/open-claude-design.ts +4 -0
- package/dist/builtin/workflows/builtin/ralph.ts +4 -0
- package/dist/builtin/workflows/builtin/tournament.ts +4 -0
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/authoring/workflow.ts +9 -0
- package/dist/builtin/workflows/src/authoring.d.ts +3 -0
- package/dist/builtin/workflows/src/durable/completed-catalog.ts +13 -2
- package/dist/builtin/workflows/src/durable/workflow-heartbeat-anchor.ts +153 -0
- package/dist/builtin/workflows/src/engine/run-durable-stage-session.ts +28 -0
- package/dist/builtin/workflows/src/engine/run.ts +1 -0
- package/dist/builtin/workflows/src/extension/extension-runtime-state.ts +138 -0
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +766 -4
- package/dist/builtin/workflows/src/extension/workflow-heartbeat-delivery.ts +197 -0
- package/dist/builtin/workflows/src/extension/workflow-heartbeat-notice.ts +115 -0
- package/dist/builtin/workflows/src/extension/workflow-heartbeat-scheduler.ts +962 -0
- package/dist/builtin/workflows/src/sdk-surface.ts +9 -0
- package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +1 -0
- package/dist/builtin/workflows/src/shared/authoring-contract-ui.ts +1 -0
- package/dist/builtin/workflows/src/shared/persistence-restore.ts +12 -0
- package/dist/builtin/workflows/src/shared/workflow-authoring-types.d.ts +1 -0
- package/dist/builtin/workflows/src/shared/workflow-authoring-types.ts +1 -0
- package/dist/builtin/workflows/src/shared/workflow-heartbeat-contract.d.ts +21 -0
- package/dist/builtin/workflows/src/shared/workflow-heartbeat-contract.ts +25 -0
- package/dist/core/extensions/index.d.ts +1 -0
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js +1 -0
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/ui-types.d.ts +26 -0
- package/dist/core/extensions/ui-types.d.ts.map +1 -1
- package/dist/core/extensions/ui-types.js +17 -1
- package/dist/core/extensions/ui-types.js.map +1 -1
- package/dist/core/tools/ask-user-question/ask-user-question.d.ts +21 -0
- package/dist/core/tools/ask-user-question/ask-user-question.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/ask-user-question.js +34 -9
- package/dist/core/tools/ask-user-question/ask-user-question.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/multi-select-view.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/multi-select-view.js +5 -2
- package/dist/core/tools/ask-user-question/view/components/multi-select-view.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/submit-picker.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/submit-picker.js +3 -1
- package/dist/core/tools/ask-user-question/view/components/submit-picker.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/wrapping-select.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/wrapping-select.js +8 -1
- package/dist/core/tools/ask-user-question/view/components/wrapping-select.js.map +1 -1
- package/dist/index-extensions.d.ts +1 -1
- package/dist/index-extensions.d.ts.map +1 -1
- package/dist/index-extensions.js +1 -1
- package/dist/index-extensions.js.map +1 -1
- package/dist/modes/interactive/components/reserved-bottom-overlay.d.ts +95 -0
- package/dist/modes/interactive/components/reserved-bottom-overlay.d.ts.map +1 -0
- package/dist/modes/interactive/components/reserved-bottom-overlay.js +234 -0
- package/dist/modes/interactive/components/reserved-bottom-overlay.js.map +1 -0
- package/dist/modes/interactive/interactive-extension-custom-ui.js +94 -6
- package/dist/modes/interactive/interactive-extension-custom-ui.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-base.d.ts +3 -0
- package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-base.js +5 -1
- package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.d.ts +2 -0
- package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
- package/dist/modes/interactive/interactive-tui.d.ts +3 -0
- package/dist/modes/interactive/interactive-tui.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-tui.js +14 -0
- package/dist/modes/interactive/interactive-tui.js.map +1 -1
- package/dist/modes/interactive-engine/engine-custom-ui.d.ts +2 -0
- package/dist/modes/interactive-engine/engine-custom-ui.d.ts.map +1 -1
- package/dist/modes/interactive-engine/engine-custom-ui.js +1 -0
- package/dist/modes/interactive-engine/engine-custom-ui.js.map +1 -1
- package/dist/modes/interactive-engine/protocol.d.ts +1 -0
- package/dist/modes/interactive-engine/protocol.d.ts.map +1 -1
- package/dist/modes/interactive-engine/protocol.js +1 -0
- package/dist/modes/interactive-engine/protocol.js.map +1 -1
- package/dist/modes/interactive-engine/remote-component.d.ts +3 -1
- package/dist/modes/interactive-engine/remote-component.d.ts.map +1 -1
- package/dist/modes/interactive-engine/remote-component.js +3 -2
- package/dist/modes/interactive-engine/remote-component.js.map +1 -1
- package/docs/extensions.md +27 -0
- package/docs/keybindings.md +1 -0
- package/docs/workflows.md +34 -0
- package/npm-shrinkwrap.json +29 -29
- package/package.json +2 -2
|
@@ -53376,6 +53376,51 @@ function registerWorkflowLifecycleHandlers(pi, deps) {
|
|
|
53376
53376
|
});
|
|
53377
53377
|
}
|
|
53378
53378
|
|
|
53379
|
+
// dist/builtin/workflows/src/durable/workflow-heartbeat-anchor.ts
|
|
53380
|
+
var WORKFLOW_HEARTBEAT_ANCHOR_CHECKPOINT_NAME = "workflow-heartbeat-anchor";
|
|
53381
|
+
function asPersistedAnchorOutput(output) {
|
|
53382
|
+
if (typeof output !== "object" || output === null || Array.isArray(output))
|
|
53383
|
+
return;
|
|
53384
|
+
const { anchorAt, intervalMinutes } = output;
|
|
53385
|
+
return {
|
|
53386
|
+
...typeof anchorAt === "number" ? { anchorAt } : {},
|
|
53387
|
+
...typeof intervalMinutes === "number" ? { intervalMinutes } : {}
|
|
53388
|
+
};
|
|
53389
|
+
}
|
|
53390
|
+
function readWorkflowHeartbeatAnchor(backend, workflowId) {
|
|
53391
|
+
const record = asPersistedAnchorOutput(backend.getToolOutput(workflowId, WORKFLOW_HEARTBEAT_ANCHOR_CHECKPOINT_NAME));
|
|
53392
|
+
if (record === undefined)
|
|
53393
|
+
return;
|
|
53394
|
+
const { anchorAt, intervalMinutes } = record;
|
|
53395
|
+
if (anchorAt === undefined || !Number.isFinite(anchorAt))
|
|
53396
|
+
return;
|
|
53397
|
+
return intervalMinutes !== undefined && Number.isFinite(intervalMinutes) && intervalMinutes > 0 ? { anchorAt, intervalMinutes } : { anchorAt };
|
|
53398
|
+
}
|
|
53399
|
+
async function recordWorkflowHeartbeatAnchor(backend, record) {
|
|
53400
|
+
if (!Number.isFinite(record.anchorAt))
|
|
53401
|
+
return false;
|
|
53402
|
+
if (!Number.isFinite(record.intervalMinutes) || record.intervalMinutes <= 0)
|
|
53403
|
+
return false;
|
|
53404
|
+
const existing = readWorkflowHeartbeatAnchor(backend, record.runId);
|
|
53405
|
+
if (existing !== undefined)
|
|
53406
|
+
return true;
|
|
53407
|
+
if (backend.listCheckpoints(record.runId).length === 0)
|
|
53408
|
+
return false;
|
|
53409
|
+
const checkpoint = {
|
|
53410
|
+
kind: "tool",
|
|
53411
|
+
workflowId: record.runId,
|
|
53412
|
+
checkpointId: WORKFLOW_HEARTBEAT_ANCHOR_CHECKPOINT_NAME,
|
|
53413
|
+
name: WORKFLOW_HEARTBEAT_ANCHOR_CHECKPOINT_NAME,
|
|
53414
|
+
argsHash: WORKFLOW_HEARTBEAT_ANCHOR_CHECKPOINT_NAME,
|
|
53415
|
+
output: { anchorAt: record.anchorAt, intervalMinutes: record.intervalMinutes },
|
|
53416
|
+
completedAt: record.now
|
|
53417
|
+
};
|
|
53418
|
+
const stored = await backend.recordAdditiveCheckpointBestEffort(checkpoint);
|
|
53419
|
+
if (!stored)
|
|
53420
|
+
return false;
|
|
53421
|
+
return readWorkflowHeartbeatAnchor(backend, record.runId) !== undefined;
|
|
53422
|
+
}
|
|
53423
|
+
|
|
53379
53424
|
// dist/builtin/workflows/src/extension/config-loader.ts
|
|
53380
53425
|
import { isAbsolute, join as join3 } from "node:path";
|
|
53381
53426
|
import { CONFIG_DIR_NAME, CONFIG_DIR_NAMES, getAgentDir, getAgentDirs, getProjectConfigPaths } from "@bastani/atomic";
|
|
@@ -53684,6 +53729,10 @@ import { Type as Type2 } from "typebox";
|
|
|
53684
53729
|
import { basename } from "node:path";
|
|
53685
53730
|
import { fileURLToPath } from "node:url";
|
|
53686
53731
|
|
|
53732
|
+
// dist/builtin/workflows/src/shared/workflow-heartbeat-contract.ts
|
|
53733
|
+
var DEFAULT_WORKFLOW_HEARTBEAT_INTERVAL_MINUTES = 15;
|
|
53734
|
+
var WORKFLOW_HEARTBEAT_CUSTOM_TYPE = "workflows:workflow-heartbeat";
|
|
53735
|
+
|
|
53687
53736
|
// dist/builtin/workflows/src/workflows/identity.ts
|
|
53688
53737
|
function normalizeWorkflowName(name) {
|
|
53689
53738
|
if (!name || typeof name !== "string") {
|
|
@@ -53777,6 +53826,10 @@ function workflow(spec) {
|
|
|
53777
53826
|
if (spec.inputs !== undefined && (spec.inputs === null || typeof spec.inputs !== "object" || Array.isArray(spec.inputs))) {
|
|
53778
53827
|
throw new TypeError("workflow: inputs must be a schema map");
|
|
53779
53828
|
}
|
|
53829
|
+
const heartbeatIntervalMinutes = spec.heartbeatIntervalMinutes === undefined ? DEFAULT_WORKFLOW_HEARTBEAT_INTERVAL_MINUTES : spec.heartbeatIntervalMinutes;
|
|
53830
|
+
if (!Number.isFinite(heartbeatIntervalMinutes) || heartbeatIntervalMinutes < 0) {
|
|
53831
|
+
throw new TypeError("workflow: heartbeatIntervalMinutes must be a non-negative finite number");
|
|
53832
|
+
}
|
|
53780
53833
|
const name = resolveWorkflowName(spec.name);
|
|
53781
53834
|
const normalizedName = normalizeWorkflowName(name);
|
|
53782
53835
|
requireNonEmptyString(normalizedName, "normalized name");
|
|
@@ -53790,6 +53843,7 @@ function workflow(spec) {
|
|
|
53790
53843
|
normalizedName,
|
|
53791
53844
|
description: spec.description,
|
|
53792
53845
|
...spec.autoAttach === true ? { autoAttach: true } : {},
|
|
53846
|
+
heartbeatIntervalMinutes,
|
|
53793
53847
|
inputs: frozenInputs,
|
|
53794
53848
|
outputs: frozenOutputs,
|
|
53795
53849
|
...inputBindings !== undefined ? { inputBindings } : {},
|
|
@@ -54184,6 +54238,7 @@ async function runAdversarialVerification(ctx) {
|
|
|
54184
54238
|
var adversarial_verification_default = workflow({
|
|
54185
54239
|
name: "adversarial-verification",
|
|
54186
54240
|
description: "Produce a candidate, challenge it with fresh-context rubric-based verifiers, and reduce their evidence through a bounded repair loop.",
|
|
54241
|
+
heartbeatIntervalMinutes: 15,
|
|
54187
54242
|
inputs: {
|
|
54188
54243
|
task: Type2.String({ description: "Task whose candidate result must be independently verified." }),
|
|
54189
54244
|
verifier_count: Type2.Integer({ minimum: 1, maximum: 5, default: 3, description: "Number of independent verifiers per review round." }),
|
|
@@ -54337,6 +54392,7 @@ var DEFAULT_ACTION_CATEGORIES = ["analysis", "implementation", "research"];
|
|
|
54337
54392
|
var classify_and_act_default = workflow({
|
|
54338
54393
|
name: "classify-and-act",
|
|
54339
54394
|
description: "Classify a task with structured confidence, route deterministically to an isolated category action, and ask for human selection when confidence is low.",
|
|
54395
|
+
heartbeatIntervalMinutes: 15,
|
|
54340
54396
|
inputs: {
|
|
54341
54397
|
prompt: Type4.String({ description: "Task to classify and execute." }),
|
|
54342
54398
|
categories: Type4.Array(Type4.String({ minLength: 1 }), {
|
|
@@ -54524,6 +54580,7 @@ async function runFanOutAndSynthesize(ctx) {
|
|
|
54524
54580
|
var fan_out_and_synthesize_default = workflow({
|
|
54525
54581
|
name: "fan-out-and-synthesize",
|
|
54526
54582
|
description: "Partition a task, run bounded independent artifact branches, then synthesize all evidence at an explicit barrier.",
|
|
54583
|
+
heartbeatIntervalMinutes: 15,
|
|
54527
54584
|
inputs: {
|
|
54528
54585
|
prompt: Type6.String({ description: "Task to partition, investigate, and synthesize." }),
|
|
54529
54586
|
max_branches: Type6.Integer({
|
|
@@ -54745,6 +54802,7 @@ async function runGenerateAndFilter(ctx) {
|
|
|
54745
54802
|
var generate_and_filter_default = workflow({
|
|
54746
54803
|
name: "generate-and-filter",
|
|
54747
54804
|
description: "Generate more independent candidates than needed, deduplicate and filter them by rubric, optionally judge them, and return a parent-consumable shortlist.",
|
|
54805
|
+
heartbeatIntervalMinutes: 15,
|
|
54748
54806
|
inputs: {
|
|
54749
54807
|
prompt: Type8.String({ description: "Prompt for candidate generation and selection." }),
|
|
54750
54808
|
num_candidates: Type8.Integer({ minimum: 2, maximum: 20, default: 8, description: "Number of independent candidates to generate." }),
|
|
@@ -56039,6 +56097,7 @@ async function runGoalWorkflow(ctx, options) {
|
|
|
56039
56097
|
var goal_default = workflow({
|
|
56040
56098
|
name: "goal",
|
|
56041
56099
|
description: "Goal Runner workflow with bounded sub-agent orchestration turns, immutable acceptance criteria, ledger artifacts, parallel reviewers, and reducer-gated completion. When launching follow-up goal runs from review findings, pass the ORIGINAL task text as acceptance_criteria so deltas cannot drift from the literal contract. If the task includes submitting a pull request (or MR/review), remove that final action from the objective text and set create_pr=true instead when preparing the workflow inputs.",
|
|
56100
|
+
heartbeatIntervalMinutes: 15,
|
|
56042
56101
|
inputs: {
|
|
56043
56102
|
objective: Type10.String({ description: "The objective or delta for this Goal Runner workflow run. Do not include PR/MR submission instructions here; strip them from the task text and request them via create_pr=true instead." }),
|
|
56044
56103
|
acceptance_criteria: Type10.Optional(Type10.String({ description: "Original immutable task contract this run must remain consistent with. Defaults to objective. Orchestrators launching follow-up runs from reviewer findings should pass the ORIGINAL task text here." })),
|
|
@@ -57269,6 +57328,7 @@ async function runOpenClaudeDesignWorkflow(ctx) {
|
|
|
57269
57328
|
var open_claude_design_default = workflow({
|
|
57270
57329
|
name: "open-claude-design",
|
|
57271
57330
|
description: "AI-powered design workflow: combined discovery/init → design-system/reference research → curated reference discovery → HTML generation → live-driven refinement → rich HTML handoff. The discovery stage asks what to build, the output type, and which references to emulate, then runs impeccable init for PRODUCT.md/DESIGN.md (references take precedence over project context). The user iteratively reviews the generated HTML.",
|
|
57331
|
+
heartbeatIntervalMinutes: 0,
|
|
57272
57332
|
inputs: {
|
|
57273
57333
|
prompt: Type12.String({
|
|
57274
57334
|
description: "What to design (for example, a dashboard, page, component, or prototype). The discovery stage refines this into a confirmed brief and asks for the output type and references."
|
|
@@ -57522,6 +57582,7 @@ async function runLoopUntilDone(ctx) {
|
|
|
57522
57582
|
var loop_until_done_default = workflow({
|
|
57523
57583
|
name: "loop-until-done",
|
|
57524
57584
|
description: "Repeat evidence-producing work and independent completion evaluation against a durable ledger until done or an inspectable iteration-limit failure.",
|
|
57585
|
+
heartbeatIntervalMinutes: 15,
|
|
57525
57586
|
inputs: {
|
|
57526
57587
|
prompt: Type14.String({ description: "Objective whose explicit completion condition controls the bounded loop." }),
|
|
57527
57588
|
max_iterations: Type14.Integer({
|
|
@@ -58477,6 +58538,7 @@ Read it before implementation or delegation because it is the primary current im
|
|
|
58477
58538
|
var ralph_default = workflow({
|
|
58478
58539
|
name: "ralph",
|
|
58479
58540
|
description: "Raw prompt → research-prompt-refinement → research → orchestrate → multi-model parallel review loop with bounded iteration and immutable acceptance criteria. When launching follow-up ralph runs from review findings, pass the ORIGINAL task text as acceptance_criteria so deltas cannot drift from the literal contract. If the task includes submitting a pull request (or MR/review), remove that final action from the prompt text and set create_pr=true instead when preparing the workflow inputs.",
|
|
58541
|
+
heartbeatIntervalMinutes: 15,
|
|
58480
58542
|
inputs: {
|
|
58481
58543
|
prompt: Type16.String({ description: "The task or goal to research, execute, and refine. Do not include PR/MR submission instructions here; strip them from the task text and request them via create_pr=true instead." }),
|
|
58482
58544
|
acceptance_criteria: Type16.Optional(Type16.String({ description: "Original immutable task contract this run must remain consistent with. Defaults to prompt. Orchestrators launching follow-up runs from reviewer findings should pass the ORIGINAL task text here." })),
|
|
@@ -58769,6 +58831,7 @@ async function runTournament(ctx) {
|
|
|
58769
58831
|
var tournament_default = workflow({
|
|
58770
58832
|
name: "tournament",
|
|
58771
58833
|
description: "Run several independent whole-task attempts through a balanced pairwise judging bracket and return an auditable winner.",
|
|
58834
|
+
heartbeatIntervalMinutes: 15,
|
|
58772
58835
|
inputs: {
|
|
58773
58836
|
prompt: Type18.String({ description: "Task every competing agent must attempt independently." }),
|
|
58774
58837
|
num_attempts: Type18.Integer({
|
|
@@ -58880,11 +58943,13 @@ __export(exports_sdk_surface, {
|
|
|
58880
58943
|
createRegistry: () => createRegistry,
|
|
58881
58944
|
createCancellationRegistry: () => createCancellationRegistry,
|
|
58882
58945
|
cancellationRegistry: () => cancellationRegistry,
|
|
58946
|
+
WORKFLOW_HEARTBEAT_CUSTOM_TYPE: () => WORKFLOW_HEARTBEAT_CUSTOM_TYPE,
|
|
58883
58947
|
NON_INTERACTIVE_WORKFLOW_POLICY: () => NON_INTERACTIVE_WORKFLOW_POLICY,
|
|
58884
58948
|
KEEP_CONTEXT_OPEN_TAG: () => KEEP_CONTEXT_OPEN_TAG,
|
|
58885
58949
|
KEEP_CONTEXT_CLOSE_TAG: () => KEEP_CONTEXT_CLOSE_TAG,
|
|
58886
58950
|
INTERACTIVE_WORKFLOW_POLICY: () => INTERACTIVE_WORKFLOW_POLICY,
|
|
58887
|
-
GraphFrontierTracker: () => GraphFrontierTracker
|
|
58951
|
+
GraphFrontierTracker: () => GraphFrontierTracker,
|
|
58952
|
+
DEFAULT_WORKFLOW_HEARTBEAT_INTERVAL_MINUTES: () => DEFAULT_WORKFLOW_HEARTBEAT_INTERVAL_MINUTES
|
|
58888
58953
|
});
|
|
58889
58954
|
|
|
58890
58955
|
// dist/builtin/workflows/src/engine/graph-inference.ts
|
|
@@ -64889,6 +64954,15 @@ function createDurableStageSessionRecorder(input) {
|
|
|
64889
64954
|
await recordRunTimingCheckpointAsync(input.deps.backend, input.runSnapshot, {
|
|
64890
64955
|
debounce: options?.forceDurable !== true
|
|
64891
64956
|
});
|
|
64957
|
+
const intervalMinutes = input.heartbeatIntervalMinutes;
|
|
64958
|
+
if (Number.isFinite(intervalMinutes * 60000)) {
|
|
64959
|
+
await recordWorkflowHeartbeatAnchor(input.deps.backend, {
|
|
64960
|
+
runId: input.runId,
|
|
64961
|
+
anchorAt: input.runSnapshot.startedAt,
|
|
64962
|
+
intervalMinutes,
|
|
64963
|
+
now: input.deps.now?.() ?? Date.now()
|
|
64964
|
+
});
|
|
64965
|
+
}
|
|
64892
64966
|
}
|
|
64893
64967
|
}
|
|
64894
64968
|
await input.onStageSession?.(stageRunId, snapshot, options);
|
|
@@ -65509,6 +65583,9 @@ async function prepareRuntimeDurableResumable(getBackend, workflowId) {
|
|
|
65509
65583
|
}
|
|
65510
65584
|
|
|
65511
65585
|
// dist/builtin/workflows/src/durable/completed-catalog.ts
|
|
65586
|
+
function isReservedRunCheckpointHash(argsHash) {
|
|
65587
|
+
return argsHash === RUN_TIMING_CHECKPOINT_NAME || argsHash === WORKFLOW_HEARTBEAT_ANCHOR_CHECKPOINT_NAME;
|
|
65588
|
+
}
|
|
65512
65589
|
function listCompletedFromBackend(backend) {
|
|
65513
65590
|
return backend.listCompletedWorkflows();
|
|
65514
65591
|
}
|
|
@@ -65625,7 +65702,7 @@ function checkpointDrafts(checkpoints) {
|
|
|
65625
65702
|
stageByReplayKey.set(checkpoint.replayKey, mergeStageDraft(existing2, checkpoint, sequence));
|
|
65626
65703
|
return;
|
|
65627
65704
|
}
|
|
65628
|
-
if (checkpoint.kind !== "tool" || checkpoint.argsHash
|
|
65705
|
+
if (checkpoint.kind !== "tool" || isReservedRunCheckpointHash(checkpoint.argsHash))
|
|
65629
65706
|
return;
|
|
65630
65707
|
if (!firstToolSequenceByHash.has(checkpoint.argsHash)) {
|
|
65631
65708
|
firstToolSequenceByHash.set(checkpoint.argsHash, sequence);
|
|
@@ -65736,7 +65813,7 @@ function runSnapshotsFromCheckpoints(checkpoints, rootRunId, rootRunName, fallba
|
|
|
65736
65813
|
}
|
|
65737
65814
|
}
|
|
65738
65815
|
if (candidates.length === 0 && toolCheckpoints.length === 0) {
|
|
65739
|
-
const supportedNonGraphState = checkpoints.every((checkpoint) => checkpoint.kind === "ui" || checkpoint.kind === "tool" && checkpoint.argsHash
|
|
65816
|
+
const supportedNonGraphState = checkpoints.every((checkpoint) => checkpoint.kind === "ui" || checkpoint.kind === "tool" && isReservedRunCheckpointHash(checkpoint.argsHash));
|
|
65740
65817
|
if (policy.failClosed && supportedNonGraphState) {
|
|
65741
65818
|
return [emptyGraphRun(rootRunId, rootRunName, fallbackCompletedAt)];
|
|
65742
65819
|
}
|
|
@@ -70634,7 +70711,8 @@ async function run(def, inputs, opts = {}) {
|
|
|
70634
70711
|
runId,
|
|
70635
70712
|
deps: durableStageDeps,
|
|
70636
70713
|
onStageSession: opts.onStageSession,
|
|
70637
|
-
...opts.parentRun === undefined ? { runSnapshot } : {}
|
|
70714
|
+
...opts.parentRun === undefined ? { runSnapshot } : {},
|
|
70715
|
+
heartbeatIntervalMinutes: def.heartbeatIntervalMinutes
|
|
70638
70716
|
});
|
|
70639
70717
|
const stageOptions = {
|
|
70640
70718
|
continuation: opts.continuation,
|
|
@@ -72299,6 +72377,628 @@ function createStatusWriter(store2, config, opts = {}) {
|
|
|
72299
72377
|
};
|
|
72300
72378
|
}
|
|
72301
72379
|
|
|
72380
|
+
// dist/builtin/workflows/src/extension/workflow-heartbeat-notice.ts
|
|
72381
|
+
var WORKFLOW_HEARTBEAT_TITLE = "WORKFLOW HEARTBEAT";
|
|
72382
|
+
var WORKFLOW_HEARTBEAT_GLYPH = "♥";
|
|
72383
|
+
var rendererRegisteredHosts5 = new WeakSet;
|
|
72384
|
+
function formatWorkflowHeartbeatNoticeText(details) {
|
|
72385
|
+
const workflowName = escapeQuotedText3(details.workflowName);
|
|
72386
|
+
const elapsed = formatWorkflowHeartbeatElapsed(details);
|
|
72387
|
+
const cadence = formatWorkflowHeartbeatCadence(details.intervalMinutes);
|
|
72388
|
+
return `${WORKFLOW_HEARTBEAT_GLYPH} Workflow "${workflowName}" heartbeat (run ${details.runId}) — still running` + `${elapsed === undefined ? "" : ` after ${elapsed}`}, on a ${cadence} cadence. ` + "This is a periodic alignment check, not a failure. Review whether the run is still on goal, then " + "continue, steer it, stop and replace it, or ask the user. " + `Inspect: /workflow status ${details.runId}`;
|
|
72389
|
+
}
|
|
72390
|
+
function registerWorkflowHeartbeatRenderer(options) {
|
|
72391
|
+
const register = options.registerMessageRenderer;
|
|
72392
|
+
if (typeof register !== "function")
|
|
72393
|
+
return;
|
|
72394
|
+
const host = options.rendererHost ?? register;
|
|
72395
|
+
if (rendererRegisteredHosts5.has(host))
|
|
72396
|
+
return;
|
|
72397
|
+
const renderer = (raw, _options, piTheme) => {
|
|
72398
|
+
const message = raw;
|
|
72399
|
+
if (!message.details)
|
|
72400
|
+
return;
|
|
72401
|
+
return makeWorkflowHeartbeatComponent(message.details, themeFromRenderer3(piTheme));
|
|
72402
|
+
};
|
|
72403
|
+
register(WORKFLOW_HEARTBEAT_CUSTOM_TYPE, renderer);
|
|
72404
|
+
rendererRegisteredHosts5.add(host);
|
|
72405
|
+
}
|
|
72406
|
+
function makeWorkflowHeartbeatComponent(details, theme) {
|
|
72407
|
+
const text = formatWorkflowHeartbeatNoticeText(details);
|
|
72408
|
+
return {
|
|
72409
|
+
render(width) {
|
|
72410
|
+
return renderWorkflowHeartbeatCard(details, { width, theme, fallbackText: text });
|
|
72411
|
+
},
|
|
72412
|
+
invalidate() {}
|
|
72413
|
+
};
|
|
72414
|
+
}
|
|
72415
|
+
function renderWorkflowHeartbeatCard(details, opts) {
|
|
72416
|
+
return renderWorkflowNoticeCard({
|
|
72417
|
+
title: WORKFLOW_HEARTBEAT_TITLE,
|
|
72418
|
+
glyph: WORKFLOW_HEARTBEAT_GLYPH,
|
|
72419
|
+
headline: `Workflow "${details.workflowName}" is still running`,
|
|
72420
|
+
tone: "mauve",
|
|
72421
|
+
fields: [
|
|
72422
|
+
{ label: "workflow", value: details.workflowName },
|
|
72423
|
+
{ label: "run", value: details.runId },
|
|
72424
|
+
{ label: "cadence", value: formatWorkflowHeartbeatCadence(details.intervalMinutes), tone: "muted" },
|
|
72425
|
+
{ label: "elapsed", value: formatWorkflowHeartbeatElapsed(details), tone: "muted" }
|
|
72426
|
+
],
|
|
72427
|
+
hints: [`/workflow status ${details.runId}`],
|
|
72428
|
+
fallbackText: opts.fallbackText,
|
|
72429
|
+
width: opts.width,
|
|
72430
|
+
...opts.theme ? { theme: opts.theme } : {}
|
|
72431
|
+
});
|
|
72432
|
+
}
|
|
72433
|
+
function formatWorkflowHeartbeatCadence(intervalMinutes) {
|
|
72434
|
+
return `${intervalMinutes}-minute`;
|
|
72435
|
+
}
|
|
72436
|
+
function formatWorkflowHeartbeatElapsed(details) {
|
|
72437
|
+
const elapsedMs = details.scheduledAt - details.startedAt;
|
|
72438
|
+
if (!Number.isFinite(elapsedMs) || elapsedMs < 0)
|
|
72439
|
+
return;
|
|
72440
|
+
const totalMinutes = Math.floor(elapsedMs / 60000);
|
|
72441
|
+
if (totalMinutes < 1)
|
|
72442
|
+
return `${Math.max(0, Math.floor(elapsedMs / 1000))}s`;
|
|
72443
|
+
if (totalMinutes < 60)
|
|
72444
|
+
return `${totalMinutes}m`;
|
|
72445
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
72446
|
+
const minutes = totalMinutes % 60;
|
|
72447
|
+
return minutes === 0 ? `${hours}h` : `${hours}h ${minutes}m`;
|
|
72448
|
+
}
|
|
72449
|
+
function escapeQuotedText3(value2) {
|
|
72450
|
+
return value2.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
72451
|
+
}
|
|
72452
|
+
function themeFromRenderer3(piTheme) {
|
|
72453
|
+
return piTheme === undefined ? undefined : deriveGraphThemeFromPiTheme(piTheme);
|
|
72454
|
+
}
|
|
72455
|
+
|
|
72456
|
+
// dist/builtin/workflows/src/extension/workflow-heartbeat-delivery.ts
|
|
72457
|
+
var defaultWorkflowHeartbeatTimerApi = {
|
|
72458
|
+
setTimeout: (handler, delayMs) => setTimeout(handler, delayMs),
|
|
72459
|
+
clearTimeout: (handle) => clearTimeout(handle)
|
|
72460
|
+
};
|
|
72461
|
+
var WORKFLOW_HEARTBEAT_MAX_DELIVERY_ATTEMPTS = 5;
|
|
72462
|
+
function createWorkflowHeartbeatDelivery(options) {
|
|
72463
|
+
const retryTimers = new Set;
|
|
72464
|
+
const queue = [];
|
|
72465
|
+
let attempt = 0;
|
|
72466
|
+
let running = false;
|
|
72467
|
+
let active2 = true;
|
|
72468
|
+
let headDiscarded = false;
|
|
72469
|
+
const settleHead = (details, delivered) => {
|
|
72470
|
+
queue.shift();
|
|
72471
|
+
attempt = 0;
|
|
72472
|
+
running = false;
|
|
72473
|
+
headDiscarded = false;
|
|
72474
|
+
options.onSettled(details, delivered);
|
|
72475
|
+
runHead();
|
|
72476
|
+
};
|
|
72477
|
+
const runHead = () => {
|
|
72478
|
+
if (!active2 || running || retryTimers.size > 0)
|
|
72479
|
+
return;
|
|
72480
|
+
const details = queue[0];
|
|
72481
|
+
if (details === undefined)
|
|
72482
|
+
return;
|
|
72483
|
+
if (options.canDeliver !== undefined && !options.canDeliver(details)) {
|
|
72484
|
+
settleHead(details, false);
|
|
72485
|
+
return;
|
|
72486
|
+
}
|
|
72487
|
+
running = true;
|
|
72488
|
+
attempt += 1;
|
|
72489
|
+
const thisAttempt = attempt;
|
|
72490
|
+
const settle = (delivered) => {
|
|
72491
|
+
if (delivered || headDiscarded || thisAttempt >= WORKFLOW_HEARTBEAT_MAX_DELIVERY_ATTEMPTS || !active2) {
|
|
72492
|
+
settleHead(details, delivered);
|
|
72493
|
+
return;
|
|
72494
|
+
}
|
|
72495
|
+
running = false;
|
|
72496
|
+
const handle = options.timers.setTimeout(() => {
|
|
72497
|
+
retryTimers.delete(handle);
|
|
72498
|
+
runHead();
|
|
72499
|
+
}, Math.min(20 * 2 ** (thisAttempt - 1), 1000));
|
|
72500
|
+
handle.unref?.();
|
|
72501
|
+
retryTimers.add(handle);
|
|
72502
|
+
};
|
|
72503
|
+
const result = options.emit(details);
|
|
72504
|
+
if (typeof result === "boolean")
|
|
72505
|
+
settle(result);
|
|
72506
|
+
else
|
|
72507
|
+
result.then(settle);
|
|
72508
|
+
};
|
|
72509
|
+
const deliver = (details) => {
|
|
72510
|
+
if (!active2)
|
|
72511
|
+
return;
|
|
72512
|
+
queue.push(details);
|
|
72513
|
+
runHead();
|
|
72514
|
+
};
|
|
72515
|
+
const discard = (runId) => {
|
|
72516
|
+
const head = queue[0];
|
|
72517
|
+
const headInFlight = running && head !== undefined && head.runId === runId;
|
|
72518
|
+
let discarded = false;
|
|
72519
|
+
for (let index = queue.length - 1;index >= 0; index -= 1) {
|
|
72520
|
+
if (queue[index]?.runId !== runId)
|
|
72521
|
+
continue;
|
|
72522
|
+
if (index === 0 && headInFlight) {
|
|
72523
|
+
headDiscarded = true;
|
|
72524
|
+
continue;
|
|
72525
|
+
}
|
|
72526
|
+
queue.splice(index, 1);
|
|
72527
|
+
discarded = true;
|
|
72528
|
+
if (index > 0)
|
|
72529
|
+
continue;
|
|
72530
|
+
attempt = 0;
|
|
72531
|
+
running = false;
|
|
72532
|
+
for (const handle of retryTimers)
|
|
72533
|
+
options.timers.clearTimeout(handle);
|
|
72534
|
+
retryTimers.clear();
|
|
72535
|
+
}
|
|
72536
|
+
if (discarded)
|
|
72537
|
+
runHead();
|
|
72538
|
+
return discarded;
|
|
72539
|
+
};
|
|
72540
|
+
return {
|
|
72541
|
+
deliver,
|
|
72542
|
+
discard,
|
|
72543
|
+
dispose() {
|
|
72544
|
+
active2 = false;
|
|
72545
|
+
for (const handle of retryTimers)
|
|
72546
|
+
options.timers.clearTimeout(handle);
|
|
72547
|
+
retryTimers.clear();
|
|
72548
|
+
queue.length = 0;
|
|
72549
|
+
}
|
|
72550
|
+
};
|
|
72551
|
+
}
|
|
72552
|
+
|
|
72553
|
+
// dist/builtin/workflows/src/extension/workflow-heartbeat-scheduler.ts
|
|
72554
|
+
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
72555
|
+
var MS_PER_MINUTE = 60000;
|
|
72556
|
+
function createWorkflowHeartbeatSchedulerState() {
|
|
72557
|
+
return {
|
|
72558
|
+
scheduled: new Map,
|
|
72559
|
+
pending: new Map,
|
|
72560
|
+
awaitingParentPickup: new Map,
|
|
72561
|
+
lastEnqueuedAt: new Map,
|
|
72562
|
+
anchorAt: new Map,
|
|
72563
|
+
anchorPersisted: new Set,
|
|
72564
|
+
anchorWritesPending: new Set,
|
|
72565
|
+
intervalMinutes: new Map
|
|
72566
|
+
};
|
|
72567
|
+
}
|
|
72568
|
+
function resetWorkflowHeartbeatSchedulerState(state2) {
|
|
72569
|
+
state2.scheduled.clear();
|
|
72570
|
+
state2.pending.clear();
|
|
72571
|
+
state2.awaitingParentPickup.clear();
|
|
72572
|
+
state2.lastEnqueuedAt.clear();
|
|
72573
|
+
state2.anchorAt.clear();
|
|
72574
|
+
state2.anchorPersisted.clear();
|
|
72575
|
+
state2.anchorWritesPending.clear();
|
|
72576
|
+
state2.intervalMinutes.clear();
|
|
72577
|
+
}
|
|
72578
|
+
var MAX_REPRESENTABLE_WORKFLOW_HEARTBEAT_INTERVAL_MINUTES = Number.MAX_VALUE / MS_PER_MINUTE;
|
|
72579
|
+
function nextWorkflowHeartbeatBoundary(startedAt, intervalMinutes, after) {
|
|
72580
|
+
if (!Number.isFinite(intervalMinutes) || intervalMinutes <= 0)
|
|
72581
|
+
return;
|
|
72582
|
+
if (!Number.isFinite(startedAt) || !Number.isFinite(after))
|
|
72583
|
+
return;
|
|
72584
|
+
const intervalMs = intervalMinutes * MS_PER_MINUTE;
|
|
72585
|
+
if (!Number.isFinite(intervalMs) || intervalMs <= 0) {
|
|
72586
|
+
warnWorkflowHeartbeatUnschedulableInterval(intervalMinutes);
|
|
72587
|
+
return;
|
|
72588
|
+
}
|
|
72589
|
+
const boundaryAt = (n) => startedAt + n * intervalMs;
|
|
72590
|
+
const elapsed = after - startedAt;
|
|
72591
|
+
const estimate = elapsed < 0 ? 1 : Math.floor(elapsed / intervalMs) + 1;
|
|
72592
|
+
if (!Number.isFinite(estimate))
|
|
72593
|
+
return;
|
|
72594
|
+
if (boundaryAt(estimate) > after)
|
|
72595
|
+
return finiteBoundaryAfter(boundaryAt(estimate), after);
|
|
72596
|
+
if (boundaryAt(estimate + 1) > after)
|
|
72597
|
+
return finiteBoundaryAfter(boundaryAt(estimate + 1), after);
|
|
72598
|
+
let low = estimate + 1;
|
|
72599
|
+
let high = low;
|
|
72600
|
+
while (boundaryAt(high) <= after) {
|
|
72601
|
+
low = high;
|
|
72602
|
+
high *= 2;
|
|
72603
|
+
if (!Number.isFinite(high))
|
|
72604
|
+
return nextRepresentableAfter(after);
|
|
72605
|
+
}
|
|
72606
|
+
while (high - low > 1) {
|
|
72607
|
+
const mid = Math.floor(low / 2 + high / 2);
|
|
72608
|
+
if (mid <= low || mid >= high)
|
|
72609
|
+
break;
|
|
72610
|
+
if (boundaryAt(mid) > after)
|
|
72611
|
+
high = mid;
|
|
72612
|
+
else
|
|
72613
|
+
low = mid;
|
|
72614
|
+
}
|
|
72615
|
+
return finiteBoundaryAfter(boundaryAt(high), after);
|
|
72616
|
+
}
|
|
72617
|
+
function finiteBoundaryAfter(scheduledAt, after) {
|
|
72618
|
+
return Number.isFinite(scheduledAt) && scheduledAt > after ? scheduledAt : undefined;
|
|
72619
|
+
}
|
|
72620
|
+
function nextRepresentableAfter(at) {
|
|
72621
|
+
if (!Number.isFinite(at))
|
|
72622
|
+
return;
|
|
72623
|
+
const view = new DataView(new ArrayBuffer(8));
|
|
72624
|
+
view.setFloat64(0, at);
|
|
72625
|
+
const bits = view.getBigUint64(0);
|
|
72626
|
+
view.setBigUint64(0, at >= 0 ? bits + 1n : bits - 1n);
|
|
72627
|
+
const next = view.getFloat64(0);
|
|
72628
|
+
return Number.isFinite(next) && next > at ? next : undefined;
|
|
72629
|
+
}
|
|
72630
|
+
function isWorkflowHeartbeatEligibleRun(run2) {
|
|
72631
|
+
if (!isTopLevelWorkflowRun(run2))
|
|
72632
|
+
return false;
|
|
72633
|
+
if (run2.status !== "running")
|
|
72634
|
+
return false;
|
|
72635
|
+
if (run2.pausedAt !== undefined)
|
|
72636
|
+
return false;
|
|
72637
|
+
return !isTerminalRunStatus(effectiveRunStatus(run2));
|
|
72638
|
+
}
|
|
72639
|
+
function isWorkflowHeartbeatTerminalRun(run2) {
|
|
72640
|
+
return isTerminalRunStatus(run2.status);
|
|
72641
|
+
}
|
|
72642
|
+
function installWorkflowHeartbeatScheduler(options) {
|
|
72643
|
+
const state2 = options.state ?? createWorkflowHeartbeatSchedulerState();
|
|
72644
|
+
const now = options.now ?? Date.now;
|
|
72645
|
+
const timers = options.timers ?? defaultWorkflowHeartbeatTimerApi;
|
|
72646
|
+
const send = options.sendMessage;
|
|
72647
|
+
const parentAvailabilityReported = options.parentAvailabilityReported === true;
|
|
72648
|
+
const anchorStore = options.anchorStore;
|
|
72649
|
+
let active2 = true;
|
|
72650
|
+
let timerHandle;
|
|
72651
|
+
const emit = (details) => {
|
|
72652
|
+
if (typeof send !== "function")
|
|
72653
|
+
return false;
|
|
72654
|
+
try {
|
|
72655
|
+
const result = send({
|
|
72656
|
+
customType: WORKFLOW_HEARTBEAT_CUSTOM_TYPE,
|
|
72657
|
+
content: formatWorkflowHeartbeatNoticeText(details),
|
|
72658
|
+
display: true,
|
|
72659
|
+
details
|
|
72660
|
+
}, { triggerTurn: true, deliverAs: "steer", persistWhenStreaming: true });
|
|
72661
|
+
if (result === undefined)
|
|
72662
|
+
return true;
|
|
72663
|
+
return Promise.resolve(result).then(() => true, (error) => {
|
|
72664
|
+
warnWorkflowHeartbeatSendFailure(error);
|
|
72665
|
+
return false;
|
|
72666
|
+
});
|
|
72667
|
+
} catch (error) {
|
|
72668
|
+
warnWorkflowHeartbeatSendFailure(error);
|
|
72669
|
+
return false;
|
|
72670
|
+
}
|
|
72671
|
+
};
|
|
72672
|
+
const canDeliverWorkflowHeartbeat = (details) => {
|
|
72673
|
+
const run2 = findRun(readGraphStoreSnapshot(options.store), details.runId);
|
|
72674
|
+
return run2 !== undefined && isWorkflowHeartbeatEligibleRun(run2);
|
|
72675
|
+
};
|
|
72676
|
+
const delivery = createWorkflowHeartbeatDelivery({
|
|
72677
|
+
emit,
|
|
72678
|
+
timers,
|
|
72679
|
+
canDeliver: canDeliverWorkflowHeartbeat,
|
|
72680
|
+
onSettled: (details, delivered) => {
|
|
72681
|
+
const identity = state2.pending.get(details.runId);
|
|
72682
|
+
if (identity?.scheduledAt !== details.scheduledAt)
|
|
72683
|
+
return;
|
|
72684
|
+
if (!delivered) {
|
|
72685
|
+
state2.pending.delete(details.runId);
|
|
72686
|
+
state2.awaitingParentPickup.delete(details.runId);
|
|
72687
|
+
refresh();
|
|
72688
|
+
return;
|
|
72689
|
+
}
|
|
72690
|
+
if (!parentAvailabilityReported) {
|
|
72691
|
+
state2.pending.delete(details.runId);
|
|
72692
|
+
refresh();
|
|
72693
|
+
return;
|
|
72694
|
+
}
|
|
72695
|
+
state2.awaitingParentPickup.set(details.runId, identity);
|
|
72696
|
+
refresh();
|
|
72697
|
+
}
|
|
72698
|
+
});
|
|
72699
|
+
const clearRunHeartbeatState = (runId) => {
|
|
72700
|
+
let cleared = state2.scheduled.delete(runId);
|
|
72701
|
+
cleared = state2.pending.delete(runId) || cleared;
|
|
72702
|
+
cleared = state2.awaitingParentPickup.delete(runId) || cleared;
|
|
72703
|
+
cleared = state2.lastEnqueuedAt.delete(runId) || cleared;
|
|
72704
|
+
cleared = state2.anchorAt.delete(runId) || cleared;
|
|
72705
|
+
cleared = state2.anchorPersisted.delete(runId) || cleared;
|
|
72706
|
+
cleared = state2.anchorWritesPending.delete(runId) || cleared;
|
|
72707
|
+
cleared = state2.intervalMinutes.delete(runId) || cleared;
|
|
72708
|
+
return delivery.discard(runId) || cleared;
|
|
72709
|
+
};
|
|
72710
|
+
const clearWorkflowHeartbeats = (runId) => {
|
|
72711
|
+
if (!clearRunHeartbeatState(runId))
|
|
72712
|
+
return { kind: "already-clear" };
|
|
72713
|
+
arm();
|
|
72714
|
+
return { kind: "cleared" };
|
|
72715
|
+
};
|
|
72716
|
+
const notifyHeartbeatConsumed = (identity) => {
|
|
72717
|
+
if (!active2)
|
|
72718
|
+
return;
|
|
72719
|
+
const held = state2.awaitingParentPickup.get(identity.runId);
|
|
72720
|
+
const pending = state2.pending.get(identity.runId);
|
|
72721
|
+
if (held?.scheduledAt !== identity.scheduledAt || pending?.scheduledAt !== identity.scheduledAt)
|
|
72722
|
+
return;
|
|
72723
|
+
state2.awaitingParentPickup.delete(identity.runId);
|
|
72724
|
+
state2.pending.delete(identity.runId);
|
|
72725
|
+
refresh();
|
|
72726
|
+
};
|
|
72727
|
+
const scheduleWorkflowHeartbeats = (run2, intervalMinutes) => {
|
|
72728
|
+
state2.scheduled.delete(run2.id);
|
|
72729
|
+
if (intervalMinutes <= 0)
|
|
72730
|
+
return { kind: "disabled" };
|
|
72731
|
+
if (run2.status !== "running")
|
|
72732
|
+
return { kind: "disabled" };
|
|
72733
|
+
const floor = Math.max(now(), state2.lastEnqueuedAt.get(run2.id) ?? Number.NEGATIVE_INFINITY);
|
|
72734
|
+
const anchorAt = resolveAnchorAt(run2);
|
|
72735
|
+
const scheduledAt = nextWorkflowHeartbeatBoundary(anchorAt, intervalMinutes, floor);
|
|
72736
|
+
if (scheduledAt === undefined)
|
|
72737
|
+
return { kind: "disabled" };
|
|
72738
|
+
persistLaunchRecordOnce(run2.id, anchorAt, intervalMinutes);
|
|
72739
|
+
state2.scheduled.set(run2.id, {
|
|
72740
|
+
runId: run2.id,
|
|
72741
|
+
scheduledAt,
|
|
72742
|
+
workflowName: run2.name,
|
|
72743
|
+
startedAt: anchorAt,
|
|
72744
|
+
intervalMinutes
|
|
72745
|
+
});
|
|
72746
|
+
return { kind: "scheduled", scheduledAt };
|
|
72747
|
+
};
|
|
72748
|
+
const enqueueWorkflowHeartbeat = (payload) => {
|
|
72749
|
+
if (!active2 || typeof send !== "function")
|
|
72750
|
+
return { kind: "suppressed", reason: "unavailable" };
|
|
72751
|
+
const run2 = findRun(readGraphStoreSnapshot(options.store), payload.runId);
|
|
72752
|
+
if (run2 === undefined)
|
|
72753
|
+
return { kind: "suppressed", reason: "missing" };
|
|
72754
|
+
if (isTerminalRunStatus(effectiveRunStatus(run2)))
|
|
72755
|
+
return { kind: "suppressed", reason: "terminal" };
|
|
72756
|
+
if (!isWorkflowHeartbeatEligibleRun(run2))
|
|
72757
|
+
return { kind: "suppressed", reason: "paused" };
|
|
72758
|
+
if (state2.pending.has(payload.runId))
|
|
72759
|
+
return { kind: "suppressed", reason: "duplicate" };
|
|
72760
|
+
if ((state2.lastEnqueuedAt.get(payload.runId) ?? Number.NEGATIVE_INFINITY) >= payload.scheduledAt) {
|
|
72761
|
+
return { kind: "suppressed", reason: "duplicate" };
|
|
72762
|
+
}
|
|
72763
|
+
const identity = { runId: payload.runId, scheduledAt: payload.scheduledAt };
|
|
72764
|
+
state2.pending.set(payload.runId, identity);
|
|
72765
|
+
state2.lastEnqueuedAt.set(payload.runId, payload.scheduledAt);
|
|
72766
|
+
delivery.deliver(payload);
|
|
72767
|
+
return { kind: "enqueued", identity };
|
|
72768
|
+
};
|
|
72769
|
+
const arm = () => {
|
|
72770
|
+
if (timerHandle !== undefined) {
|
|
72771
|
+
timers.clearTimeout(timerHandle);
|
|
72772
|
+
timerHandle = undefined;
|
|
72773
|
+
}
|
|
72774
|
+
if (!active2)
|
|
72775
|
+
return;
|
|
72776
|
+
const next = earliestScheduled(state2);
|
|
72777
|
+
if (next === undefined)
|
|
72778
|
+
return;
|
|
72779
|
+
const nextAt = next.scheduledAt;
|
|
72780
|
+
const delay2 = Math.min(Math.max(1, Math.ceil(nextAt - now())), MAX_TIMER_DELAY_MS);
|
|
72781
|
+
const handle = timers.setTimeout(() => {
|
|
72782
|
+
timerHandle = undefined;
|
|
72783
|
+
processDue();
|
|
72784
|
+
}, delay2);
|
|
72785
|
+
handle.unref?.();
|
|
72786
|
+
timerHandle = handle;
|
|
72787
|
+
};
|
|
72788
|
+
const refresh = () => {
|
|
72789
|
+
if (!active2 || typeof send !== "function")
|
|
72790
|
+
return;
|
|
72791
|
+
const snapshot = readGraphStoreSnapshot(options.store);
|
|
72792
|
+
const observed = new Set;
|
|
72793
|
+
for (const run2 of snapshot.runs) {
|
|
72794
|
+
if (!isTopLevelWorkflowRun(run2))
|
|
72795
|
+
continue;
|
|
72796
|
+
if (isWorkflowHeartbeatTerminalRun(run2)) {
|
|
72797
|
+
clearWorkflowHeartbeats(run2.id);
|
|
72798
|
+
continue;
|
|
72799
|
+
}
|
|
72800
|
+
observed.add(run2.id);
|
|
72801
|
+
if (state2.pending.has(run2.id) || !isWorkflowHeartbeatEligibleRun(run2)) {
|
|
72802
|
+
state2.scheduled.delete(run2.id);
|
|
72803
|
+
continue;
|
|
72804
|
+
}
|
|
72805
|
+
const owed = state2.scheduled.get(run2.id);
|
|
72806
|
+
if (owed !== undefined && owed.scheduledAt <= now())
|
|
72807
|
+
continue;
|
|
72808
|
+
const launched = readLaunchRecord(run2.id);
|
|
72809
|
+
const intervalMinutes = resolveRunIntervalMinutes(state2, options.resolveIntervalMinutes, run2, launched?.intervalMinutes);
|
|
72810
|
+
if (intervalMinutes === undefined) {
|
|
72811
|
+
state2.scheduled.delete(run2.id);
|
|
72812
|
+
continue;
|
|
72813
|
+
}
|
|
72814
|
+
scheduleWorkflowHeartbeats(run2, intervalMinutes);
|
|
72815
|
+
}
|
|
72816
|
+
for (const runId of trackedRunIds(state2)) {
|
|
72817
|
+
if (!observed.has(runId))
|
|
72818
|
+
clearWorkflowHeartbeats(runId);
|
|
72819
|
+
}
|
|
72820
|
+
arm();
|
|
72821
|
+
};
|
|
72822
|
+
function readLaunchRecord(runId) {
|
|
72823
|
+
try {
|
|
72824
|
+
return anchorStore?.readAnchorAt(runId);
|
|
72825
|
+
} catch {
|
|
72826
|
+
return;
|
|
72827
|
+
}
|
|
72828
|
+
}
|
|
72829
|
+
function resolveAnchorAt(run2) {
|
|
72830
|
+
const cached = state2.anchorAt.get(run2.id);
|
|
72831
|
+
if (cached !== undefined)
|
|
72832
|
+
return Math.min(cached, run2.startedAt);
|
|
72833
|
+
const stored = readLaunchRecord(run2.id)?.anchorAt;
|
|
72834
|
+
const anchorAt = stored === undefined ? run2.startedAt : Math.min(stored, run2.startedAt);
|
|
72835
|
+
state2.anchorAt.set(run2.id, anchorAt);
|
|
72836
|
+
return anchorAt;
|
|
72837
|
+
}
|
|
72838
|
+
function persistLaunchRecordOnce(runId, anchorAt, intervalMinutes) {
|
|
72839
|
+
if (anchorStore === undefined)
|
|
72840
|
+
return;
|
|
72841
|
+
if (state2.anchorPersisted.has(runId) || state2.anchorWritesPending.has(runId))
|
|
72842
|
+
return;
|
|
72843
|
+
state2.anchorWritesPending.add(runId);
|
|
72844
|
+
let write;
|
|
72845
|
+
try {
|
|
72846
|
+
write = Promise.resolve(anchorStore.recordAnchorAt(runId, { anchorAt, intervalMinutes }));
|
|
72847
|
+
} catch {
|
|
72848
|
+
state2.anchorWritesPending.delete(runId);
|
|
72849
|
+
return;
|
|
72850
|
+
}
|
|
72851
|
+
write.then((acknowledged) => {
|
|
72852
|
+
const tracked2 = state2.anchorWritesPending.delete(runId);
|
|
72853
|
+
if (acknowledged && tracked2)
|
|
72854
|
+
state2.anchorPersisted.add(runId);
|
|
72855
|
+
}, () => {
|
|
72856
|
+
state2.anchorWritesPending.delete(runId);
|
|
72857
|
+
});
|
|
72858
|
+
}
|
|
72859
|
+
const processDue = () => {
|
|
72860
|
+
if (!active2)
|
|
72861
|
+
return;
|
|
72862
|
+
const at = now();
|
|
72863
|
+
const due = [...state2.scheduled.values()].filter((details) => details.scheduledAt <= at).sort(compareWorkflowHeartbeatOrder);
|
|
72864
|
+
const snapshot = readGraphStoreSnapshot(options.store);
|
|
72865
|
+
for (const details of due) {
|
|
72866
|
+
state2.scheduled.delete(details.runId);
|
|
72867
|
+
const run2 = findRun(snapshot, details.runId);
|
|
72868
|
+
if (run2 === undefined || !isWorkflowHeartbeatEligibleRun(run2))
|
|
72869
|
+
continue;
|
|
72870
|
+
enqueueWorkflowHeartbeat(details);
|
|
72871
|
+
}
|
|
72872
|
+
refresh();
|
|
72873
|
+
};
|
|
72874
|
+
const unsubscribe = subscribeStoreInvalidation(options.store, refresh);
|
|
72875
|
+
refresh();
|
|
72876
|
+
return {
|
|
72877
|
+
scheduleWorkflowHeartbeats,
|
|
72878
|
+
enqueueWorkflowHeartbeat,
|
|
72879
|
+
clearWorkflowHeartbeats,
|
|
72880
|
+
notifyHeartbeatConsumed,
|
|
72881
|
+
state: state2,
|
|
72882
|
+
dispose() {
|
|
72883
|
+
active2 = false;
|
|
72884
|
+
unsubscribe();
|
|
72885
|
+
if (timerHandle !== undefined) {
|
|
72886
|
+
timers.clearTimeout(timerHandle);
|
|
72887
|
+
timerHandle = undefined;
|
|
72888
|
+
}
|
|
72889
|
+
state2.scheduled.clear();
|
|
72890
|
+
delivery.dispose();
|
|
72891
|
+
}
|
|
72892
|
+
};
|
|
72893
|
+
}
|
|
72894
|
+
function compareWorkflowHeartbeatOrder(a, b) {
|
|
72895
|
+
if (a.scheduledAt !== b.scheduledAt)
|
|
72896
|
+
return a.scheduledAt - b.scheduledAt;
|
|
72897
|
+
if (a.runId === b.runId)
|
|
72898
|
+
return 0;
|
|
72899
|
+
return a.runId < b.runId ? -1 : 1;
|
|
72900
|
+
}
|
|
72901
|
+
function earliestScheduled(state2) {
|
|
72902
|
+
let earliest;
|
|
72903
|
+
for (const details of state2.scheduled.values()) {
|
|
72904
|
+
if (earliest === undefined || compareWorkflowHeartbeatOrder(details, earliest) < 0)
|
|
72905
|
+
earliest = details;
|
|
72906
|
+
}
|
|
72907
|
+
return earliest;
|
|
72908
|
+
}
|
|
72909
|
+
function trackedRunIds(state2) {
|
|
72910
|
+
const ids = new Set([
|
|
72911
|
+
...state2.scheduled.keys(),
|
|
72912
|
+
...state2.pending.keys(),
|
|
72913
|
+
...state2.awaitingParentPickup.keys(),
|
|
72914
|
+
...state2.lastEnqueuedAt.keys(),
|
|
72915
|
+
...state2.anchorAt.keys(),
|
|
72916
|
+
...state2.anchorPersisted,
|
|
72917
|
+
...state2.anchorWritesPending,
|
|
72918
|
+
...state2.intervalMinutes.keys()
|
|
72919
|
+
]);
|
|
72920
|
+
return [...ids];
|
|
72921
|
+
}
|
|
72922
|
+
function resolveRunIntervalMinutes(state2, resolve14, run2, persistedIntervalMinutes) {
|
|
72923
|
+
const memoized = state2.intervalMinutes.get(run2.id);
|
|
72924
|
+
if (memoized !== undefined)
|
|
72925
|
+
return memoized > 0 ? memoized : undefined;
|
|
72926
|
+
const resolved = persistedIntervalMinutes ?? resolve14(run2.name);
|
|
72927
|
+
if (resolved === undefined || !Number.isFinite(resolved))
|
|
72928
|
+
return;
|
|
72929
|
+
state2.intervalMinutes.set(run2.id, resolved);
|
|
72930
|
+
return resolved > 0 ? resolved : undefined;
|
|
72931
|
+
}
|
|
72932
|
+
function findRun(snapshot, runId) {
|
|
72933
|
+
return snapshot.runs.find((run2) => run2.id === runId);
|
|
72934
|
+
}
|
|
72935
|
+
function warnWorkflowHeartbeatSendFailure(error) {
|
|
72936
|
+
if (process.env.ATOMIC_WORKFLOW_DEBUG !== "1")
|
|
72937
|
+
return;
|
|
72938
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
72939
|
+
console.warn("[workflows] workflow heartbeat send failed", message);
|
|
72940
|
+
}
|
|
72941
|
+
function warnWorkflowHeartbeatUnschedulableInterval(intervalMinutes) {
|
|
72942
|
+
if (process.env.ATOMIC_WORKFLOW_DEBUG !== "1")
|
|
72943
|
+
return;
|
|
72944
|
+
console.warn(`[workflows] heartbeatIntervalMinutes ${intervalMinutes} exceeds the largest representable cadence ` + `(${MAX_REPRESENTABLE_WORKFLOW_HEARTBEAT_INTERVAL_MINUTES} minutes); no boundary can be scheduled for it`);
|
|
72945
|
+
}
|
|
72946
|
+
function isRecord4(value2) {
|
|
72947
|
+
return typeof value2 === "object" && value2 !== null;
|
|
72948
|
+
}
|
|
72949
|
+
function asHostMessageEndEvent(event) {
|
|
72950
|
+
if (!isRecord4(event))
|
|
72951
|
+
return;
|
|
72952
|
+
const message = event.message;
|
|
72953
|
+
if (!isRecord4(message))
|
|
72954
|
+
return;
|
|
72955
|
+
const { role, customType, details } = message;
|
|
72956
|
+
if (typeof role !== "string")
|
|
72957
|
+
return;
|
|
72958
|
+
return {
|
|
72959
|
+
message: {
|
|
72960
|
+
role,
|
|
72961
|
+
...typeof customType === "string" ? { customType } : {},
|
|
72962
|
+
...isRecord4(details) ? { details } : {},
|
|
72963
|
+
raw: message
|
|
72964
|
+
}
|
|
72965
|
+
};
|
|
72966
|
+
}
|
|
72967
|
+
var PROTECTED_RECONCILIATION_CUSTOM_TYPE = "atomic:protected-streaming-reconciliation";
|
|
72968
|
+
function workflowHeartbeatConsumedIdentity(event, entries) {
|
|
72969
|
+
if (entries === undefined)
|
|
72970
|
+
return;
|
|
72971
|
+
const parsed = asHostMessageEndEvent(event);
|
|
72972
|
+
if (parsed === undefined)
|
|
72973
|
+
return;
|
|
72974
|
+
const { role, customType, details } = parsed.message;
|
|
72975
|
+
if (role !== "custom" || customType !== PROTECTED_RECONCILIATION_CUSTOM_TYPE)
|
|
72976
|
+
return;
|
|
72977
|
+
if (details === undefined)
|
|
72978
|
+
return;
|
|
72979
|
+
const intentEntryId = details.protectedReconciliationOf;
|
|
72980
|
+
if (typeof intentEntryId !== "string")
|
|
72981
|
+
return;
|
|
72982
|
+
const intent = entries.find((entry) => entry.id === intentEntryId);
|
|
72983
|
+
if (intent?.customType !== WORKFLOW_HEARTBEAT_CUSTOM_TYPE)
|
|
72984
|
+
return;
|
|
72985
|
+
if (!isRecord4(intent.details))
|
|
72986
|
+
return;
|
|
72987
|
+
const { runId, scheduledAt } = intent.details;
|
|
72988
|
+
if (typeof runId !== "string" || typeof scheduledAt !== "number")
|
|
72989
|
+
return;
|
|
72990
|
+
return { runId, scheduledAt };
|
|
72991
|
+
}
|
|
72992
|
+
function workflowHeartbeatContextInvalidation(event, entries, isIdentityOwned) {
|
|
72993
|
+
const identity = workflowHeartbeatConsumedIdentity(event, entries);
|
|
72994
|
+
if (identity === undefined || isIdentityOwned(identity))
|
|
72995
|
+
return;
|
|
72996
|
+
const parsed = asHostMessageEndEvent(event);
|
|
72997
|
+
if (parsed === undefined)
|
|
72998
|
+
return;
|
|
72999
|
+
return { message: { ...parsed.message.raw, excludeFromContext: true } };
|
|
73000
|
+
}
|
|
73001
|
+
|
|
72302
73002
|
// dist/builtin/workflows/src/extension/workflow-model-catalog.ts
|
|
72303
73003
|
function workflowModelCatalogFromContext(ctx) {
|
|
72304
73004
|
if (ctx?.modelRegistry === undefined && ctx?.model === undefined)
|
|
@@ -72463,6 +73163,29 @@ function workflowReloadDiagnostics(configDiagnostics, discoveryDiagnostics) {
|
|
|
72463
73163
|
}
|
|
72464
73164
|
|
|
72465
73165
|
// dist/builtin/workflows/src/extension/extension-runtime-state.ts
|
|
73166
|
+
function durableWorkflowHeartbeatAnchorStore() {
|
|
73167
|
+
return {
|
|
73168
|
+
readAnchorAt(runId) {
|
|
73169
|
+
try {
|
|
73170
|
+
return readWorkflowHeartbeatAnchor(getDurableBackend(), runId);
|
|
73171
|
+
} catch {
|
|
73172
|
+
return;
|
|
73173
|
+
}
|
|
73174
|
+
},
|
|
73175
|
+
async recordAnchorAt(runId, record) {
|
|
73176
|
+
try {
|
|
73177
|
+
return await recordWorkflowHeartbeatAnchor(getDurableBackend(), {
|
|
73178
|
+
runId,
|
|
73179
|
+
anchorAt: record.anchorAt,
|
|
73180
|
+
intervalMinutes: record.intervalMinutes ?? 0,
|
|
73181
|
+
now: Date.now()
|
|
73182
|
+
});
|
|
73183
|
+
} catch {
|
|
73184
|
+
return false;
|
|
73185
|
+
}
|
|
73186
|
+
}
|
|
73187
|
+
};
|
|
73188
|
+
}
|
|
72466
73189
|
function createWorkflowExtensionRuntimeState(pi, adapters, resolveCwd = () => pi.sessionManager?.getCwd?.() ?? process.cwd()) {
|
|
72467
73190
|
const persistenceRef = { current: makePersistencePort(pi, WORKFLOW_CONFIG_DEFAULTS.persistRuns) };
|
|
72468
73191
|
const mcpPort = makeMcpPort(pi);
|
|
@@ -72479,10 +73202,12 @@ function createWorkflowExtensionRuntimeState(pi, adapters, resolveCwd = () => pi
|
|
|
72479
73202
|
let statusWriterRef = createStatusWriter(store, runtimeConfigRef.current);
|
|
72480
73203
|
let lifecycleNotificationsUnsubscribe = null;
|
|
72481
73204
|
let hilAnswerNotificationsUnsubscribe = null;
|
|
73205
|
+
let workflowHeartbeatScheduler = null;
|
|
72482
73206
|
let notificationsActive = false;
|
|
72483
73207
|
let notificationGeneration = 0;
|
|
72484
73208
|
const lifecycleNotificationState = createWorkflowLifecycleNotificationState();
|
|
72485
73209
|
const hilAnswerNotificationState = createWorkflowHilAnswerNotificationState();
|
|
73210
|
+
const workflowHeartbeatSchedulerState = createWorkflowHeartbeatSchedulerState();
|
|
72486
73211
|
const beforeRestoreCompleted = (snapshots) => {
|
|
72487
73212
|
seedWorkflowLifecycleNotificationState(lifecycleNotificationState, {
|
|
72488
73213
|
...readGraphStoreSnapshot(store),
|
|
@@ -72495,6 +73220,7 @@ function createWorkflowExtensionRuntimeState(pi, adapters, resolveCwd = () => pi
|
|
|
72495
73220
|
const registerMessageRenderer = typeof pi.registerMessageRenderer === "function" ? (event, renderer) => pi.registerMessageRenderer(event, renderer) : undefined;
|
|
72496
73221
|
registerLifecycleNoticeRenderer({ rendererHost: pi, registerMessageRenderer });
|
|
72497
73222
|
registerHilAnswerNoticeRenderer({ rendererHost: pi, registerMessageRenderer });
|
|
73223
|
+
registerWorkflowHeartbeatRenderer({ rendererHost: pi, registerMessageRenderer });
|
|
72498
73224
|
const sendWorkflowNotificationMessage = typeof pi.sendMessage === "function" ? (message, options) => pi.sendMessage(message, options) : undefined;
|
|
72499
73225
|
const reinstallLifecycleNotifications = () => {
|
|
72500
73226
|
lifecycleNotificationsUnsubscribe?.();
|
|
@@ -72521,6 +73247,38 @@ function createWorkflowExtensionRuntimeState(pi, adapters, resolveCwd = () => pi
|
|
|
72521
73247
|
sendMessage: sendWorkflowNotificationMessage
|
|
72522
73248
|
});
|
|
72523
73249
|
};
|
|
73250
|
+
const parentAvailabilityReported = typeof pi.on === "function";
|
|
73251
|
+
if (parentAvailabilityReported) {
|
|
73252
|
+
pi.on?.("message_end", (event, ctx) => {
|
|
73253
|
+
const entries = (ctx?.sessionManager ?? pi.sessionManager)?.getEntries?.();
|
|
73254
|
+
const invalidation = workflowHeartbeatContextInvalidation(event, entries, isWorkflowHeartbeatIdentityOwned);
|
|
73255
|
+
if (invalidation !== undefined)
|
|
73256
|
+
return invalidation;
|
|
73257
|
+
const identity = workflowHeartbeatConsumedIdentity(event, entries);
|
|
73258
|
+
if (identity !== undefined)
|
|
73259
|
+
workflowHeartbeatScheduler?.notifyHeartbeatConsumed(identity);
|
|
73260
|
+
return;
|
|
73261
|
+
});
|
|
73262
|
+
}
|
|
73263
|
+
function isWorkflowHeartbeatIdentityOwned(identity) {
|
|
73264
|
+
const pending = workflowHeartbeatSchedulerState.pending.get(identity.runId);
|
|
73265
|
+
const run2 = store.runs().find((candidate) => candidate.id === identity.runId);
|
|
73266
|
+
return pending?.scheduledAt === identity.scheduledAt && run2 !== undefined && !isWorkflowHeartbeatTerminalRun(run2);
|
|
73267
|
+
}
|
|
73268
|
+
const reinstallWorkflowHeartbeatScheduler = () => {
|
|
73269
|
+
workflowHeartbeatScheduler?.dispose();
|
|
73270
|
+
workflowHeartbeatScheduler = null;
|
|
73271
|
+
if (!notificationsActive)
|
|
73272
|
+
return;
|
|
73273
|
+
workflowHeartbeatScheduler = installWorkflowHeartbeatScheduler({
|
|
73274
|
+
store,
|
|
73275
|
+
state: workflowHeartbeatSchedulerState,
|
|
73276
|
+
sendMessage: sendWorkflowNotificationMessage,
|
|
73277
|
+
resolveIntervalMinutes: (workflowName) => runtimeProxy.registry.get(workflowName)?.heartbeatIntervalMinutes,
|
|
73278
|
+
parentAvailabilityReported,
|
|
73279
|
+
anchorStore: durableWorkflowHeartbeatAnchorStore()
|
|
73280
|
+
});
|
|
73281
|
+
};
|
|
72524
73282
|
const hostStageSessionDir = { current: undefined };
|
|
72525
73283
|
const resolveDefaultStageSessionDir = () => hostStageSessionDir.current;
|
|
72526
73284
|
const startupDiscovery = discoverStartupWorkflowsSync();
|
|
@@ -72789,6 +73547,7 @@ function createWorkflowExtensionRuntimeState(pi, adapters, resolveCwd = () => pi
|
|
|
72789
73547
|
discoveryRef,
|
|
72790
73548
|
lifecycleNotificationState,
|
|
72791
73549
|
hilAnswerNotificationState,
|
|
73550
|
+
workflowHeartbeatSchedulerState,
|
|
72792
73551
|
beforeRestoreCompleted,
|
|
72793
73552
|
runtimeForContext,
|
|
72794
73553
|
resetWorkflowDiscoveryForSession,
|
|
@@ -72804,6 +73563,9 @@ function createWorkflowExtensionRuntimeState(pi, adapters, resolveCwd = () => pi
|
|
|
72804
73563
|
notificationsActive = active2;
|
|
72805
73564
|
reinstallLifecycleNotifications();
|
|
72806
73565
|
reinstallHilAnswerNotifications();
|
|
73566
|
+
if (!active2)
|
|
73567
|
+
resetWorkflowHeartbeatSchedulerState(workflowHeartbeatSchedulerState);
|
|
73568
|
+
reinstallWorkflowHeartbeatScheduler();
|
|
72807
73569
|
},
|
|
72808
73570
|
updateHostStageSessionDir(sessionManager) {
|
|
72809
73571
|
try {
|