@nodetool-ai/websocket 0.8.1 → 0.8.2
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/.nodetool-build-stamp +1 -1
- package/dist/collection-api.d.ts.map +1 -1
- package/dist/collection-api.js +41 -4
- package/dist/collection-api.js.map +1 -1
- package/dist/error-traces.d.ts +56 -0
- package/dist/error-traces.d.ts.map +1 -0
- package/dist/error-traces.js +179 -0
- package/dist/error-traces.js.map +1 -0
- package/dist/fake-runtime.d.ts.map +1 -1
- package/dist/fake-runtime.js +40 -3
- package/dist/fake-runtime.js.map +1 -1
- package/dist/generation-recovery.d.ts.map +1 -1
- package/dist/generation-recovery.js +26 -4
- package/dist/generation-recovery.js.map +1 -1
- package/dist/http-api.d.ts.map +1 -1
- package/dist/http-api.js +15 -0
- package/dist/http-api.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/interrupted-jobs.d.ts +14 -0
- package/dist/interrupted-jobs.d.ts.map +1 -0
- package/dist/interrupted-jobs.js +28 -0
- package/dist/interrupted-jobs.js.map +1 -0
- package/dist/lib/app-debug-service.js +1 -1
- package/dist/lib/app-debug-service.js.map +1 -1
- package/dist/lib/app-instances-service.d.ts +1284 -0
- package/dist/lib/app-instances-service.d.ts.map +1 -0
- package/dist/lib/app-instances-service.js +378 -0
- package/dist/lib/app-instances-service.js.map +1 -0
- package/dist/lib/app-run-cancellation.d.ts +4 -0
- package/dist/lib/app-run-cancellation.d.ts.map +1 -0
- package/dist/lib/app-run-cancellation.js +28 -0
- package/dist/lib/app-run-cancellation.js.map +1 -0
- package/dist/lib/applications-service.d.ts +182 -1
- package/dist/lib/applications-service.d.ts.map +1 -1
- package/dist/lib/applications-service.js +20 -4
- package/dist/lib/applications-service.js.map +1 -1
- package/dist/lib/asset-paths.d.ts +7 -11
- package/dist/lib/asset-paths.d.ts.map +1 -1
- package/dist/lib/asset-paths.js +8 -56
- package/dist/lib/asset-paths.js.map +1 -1
- package/dist/lib/asset-response.d.ts.map +1 -1
- package/dist/lib/asset-response.js +2 -1
- package/dist/lib/asset-response.js.map +1 -1
- package/dist/lib/audio-peaks.d.ts.map +1 -1
- package/dist/lib/audio-peaks.js +5 -1
- package/dist/lib/audio-peaks.js.map +1 -1
- package/dist/lib/bridge.d.ts.map +1 -1
- package/dist/lib/bridge.js.map +1 -1
- package/dist/lib/document-model-interfaces.d.ts.map +1 -1
- package/dist/lib/document-model-interfaces.js +9 -8
- package/dist/lib/document-model-interfaces.js.map +1 -1
- package/dist/lib/example-apps.d.ts +9 -0
- package/dist/lib/example-apps.d.ts.map +1 -1
- package/dist/lib/example-apps.js +61 -14
- package/dist/lib/example-apps.js.map +1 -1
- package/dist/lib/http-tracing.d.ts +12 -0
- package/dist/lib/http-tracing.d.ts.map +1 -0
- package/dist/lib/http-tracing.js +81 -0
- package/dist/lib/http-tracing.js.map +1 -0
- package/dist/lib/media.d.ts +1 -6
- package/dist/lib/media.d.ts.map +1 -1
- package/dist/lib/media.js +8 -44
- package/dist/lib/media.js.map +1 -1
- package/dist/lib/public-routes.d.ts +7 -0
- package/dist/lib/public-routes.d.ts.map +1 -1
- package/dist/lib/public-routes.js +14 -0
- package/dist/lib/public-routes.js.map +1 -1
- package/dist/lib/public-workflow-share.d.ts +20 -0
- package/dist/lib/public-workflow-share.d.ts.map +1 -0
- package/dist/lib/public-workflow-share.js +45 -0
- package/dist/lib/public-workflow-share.js.map +1 -0
- package/dist/lib/run-trace-live-delivery.d.ts +25 -0
- package/dist/lib/run-trace-live-delivery.d.ts.map +1 -0
- package/dist/lib/run-trace-live-delivery.js +91 -0
- package/dist/lib/run-trace-live-delivery.js.map +1 -0
- package/dist/lib/test-ui-run-routes.d.ts +8 -0
- package/dist/lib/test-ui-run-routes.d.ts.map +1 -0
- package/dist/lib/test-ui-run-routes.js +42 -0
- package/dist/lib/test-ui-run-routes.js.map +1 -0
- package/dist/lib/thumbnail.d.ts.map +1 -1
- package/dist/lib/thumbnail.js +12 -9
- package/dist/lib/thumbnail.js.map +1 -1
- package/dist/lib/timeline-bundle.d.ts +148 -16
- package/dist/lib/timeline-bundle.d.ts.map +1 -1
- package/dist/lib/timeline-bundle.js +12 -0
- package/dist/lib/timeline-bundle.js.map +1 -1
- package/dist/lib/video-proxy.d.ts.map +1 -1
- package/dist/lib/video-proxy.js +3 -2
- package/dist/lib/video-proxy.js.map +1 -1
- package/dist/lib/workflow-service.d.ts.map +1 -1
- package/dist/lib/workflow-service.js +4 -1
- package/dist/lib/workflow-service.js.map +1 -1
- package/dist/mcp-agent-tools.d.ts.map +1 -1
- package/dist/mcp-agent-tools.js +93 -162
- package/dist/mcp-agent-tools.js.map +1 -1
- package/dist/mcp-client-config.d.ts +64 -0
- package/dist/mcp-client-config.d.ts.map +1 -0
- package/dist/mcp-client-config.js +353 -0
- package/dist/mcp-client-config.js.map +1 -0
- package/dist/models-api.d.ts +10 -0
- package/dist/models-api.d.ts.map +1 -1
- package/dist/models-api.js +23 -0
- package/dist/models-api.js.map +1 -1
- package/dist/node-registry-setup.d.ts +1 -1
- package/dist/node-registry-setup.d.ts.map +1 -1
- package/dist/node-registry-setup.js +21 -114
- package/dist/node-registry-setup.js.map +1 -1
- package/dist/plugins/websocket.d.ts.map +1 -1
- package/dist/plugins/websocket.js +20 -57
- package/dist/plugins/websocket.js.map +1 -1
- package/dist/python-bridge-gate.d.ts +28 -0
- package/dist/python-bridge-gate.d.ts.map +1 -0
- package/dist/python-bridge-gate.js +36 -0
- package/dist/python-bridge-gate.js.map +1 -0
- package/dist/routes/app-runs.d.ts +4 -0
- package/dist/routes/app-runs.d.ts.map +1 -0
- package/dist/routes/app-runs.js +173 -0
- package/dist/routes/app-runs.js.map +1 -0
- package/dist/routes/js-scripts.d.ts +9 -3
- package/dist/routes/js-scripts.d.ts.map +1 -1
- package/dist/routes/js-scripts.js +450 -68
- package/dist/routes/js-scripts.js.map +1 -1
- package/dist/routes/nodes.d.ts.map +1 -1
- package/dist/routes/nodes.js +32 -1
- package/dist/routes/nodes.js.map +1 -1
- package/dist/routes/public-shared-workflows.d.ts +16 -0
- package/dist/routes/public-shared-workflows.d.ts.map +1 -0
- package/dist/routes/public-shared-workflows.js +55 -0
- package/dist/routes/public-shared-workflows.js.map +1 -0
- package/dist/routes/run-spans.d.ts +4 -0
- package/dist/routes/run-spans.d.ts.map +1 -0
- package/dist/routes/run-spans.js +53 -0
- package/dist/routes/run-spans.js.map +1 -0
- package/dist/routes/timeline-fonts.d.ts +22 -0
- package/dist/routes/timeline-fonts.d.ts.map +1 -1
- package/dist/routes/timeline-fonts.js +138 -7
- package/dist/routes/timeline-fonts.js.map +1 -1
- package/dist/screenshot-server.js +4 -5
- package/dist/screenshot-server.js.map +1 -1
- package/dist/sdk/sdk-model-download-service.d.ts.map +1 -1
- package/dist/sdk/sdk-model-download-service.js +5 -2
- package/dist/sdk/sdk-model-download-service.js.map +1 -1
- package/dist/server.js +86 -19
- package/dist/server.js.map +1 -1
- package/dist/session/app-run-fold.d.ts +7 -0
- package/dist/session/app-run-fold.d.ts.map +1 -0
- package/dist/session/app-run-fold.js +45 -0
- package/dist/session/app-run-fold.js.map +1 -0
- package/dist/session/chat-prompt.d.ts.map +1 -1
- package/dist/session/chat-prompt.js +37 -15
- package/dist/session/chat-prompt.js.map +1 -1
- package/dist/session/chat-turn.d.ts +1 -0
- package/dist/session/chat-turn.d.ts.map +1 -1
- package/dist/session/chat-turn.js +203 -107
- package/dist/session/chat-turn.js.map +1 -1
- package/dist/session/commands.d.ts.map +1 -1
- package/dist/session/commands.js +6 -0
- package/dist/session/commands.js.map +1 -1
- package/dist/session/inference.d.ts +5 -0
- package/dist/session/inference.d.ts.map +1 -1
- package/dist/session/inference.js +80 -9
- package/dist/session/inference.js.map +1 -1
- package/dist/session/job-execution.d.ts +7 -0
- package/dist/session/job-execution.d.ts.map +1 -1
- package/dist/session/job-execution.js +297 -7
- package/dist/session/job-execution.js.map +1 -1
- package/dist/session/model-interfaces.d.ts +2 -1
- package/dist/session/model-interfaces.d.ts.map +1 -1
- package/dist/session/model-interfaces.js +1 -0
- package/dist/session/model-interfaces.js.map +1 -1
- package/dist/storage-retention.d.ts +5 -0
- package/dist/storage-retention.d.ts.map +1 -1
- package/dist/storage-retention.js +22 -1
- package/dist/storage-retention.js.map +1 -1
- package/dist/test-ui-server.d.ts.map +1 -1
- package/dist/test-ui-server.js +21 -3
- package/dist/test-ui-server.js.map +1 -1
- package/dist/timeline-generation-recovery.d.ts +4 -0
- package/dist/timeline-generation-recovery.d.ts.map +1 -0
- package/dist/timeline-generation-recovery.js +72 -0
- package/dist/timeline-generation-recovery.js.map +1 -0
- package/dist/triggers/dispatcher.js +2 -2
- package/dist/triggers/dispatcher.js.map +1 -1
- package/dist/triggers/stores.js +8 -8
- package/dist/triggers/stores.js.map +1 -1
- package/dist/triggers/webhook-route.js +2 -2
- package/dist/triggers/webhook-route.js.map +1 -1
- package/dist/trpc/error-logging.d.ts +10 -0
- package/dist/trpc/error-logging.d.ts.map +1 -1
- package/dist/trpc/error-logging.js +21 -0
- package/dist/trpc/error-logging.js.map +1 -1
- package/dist/trpc/router.d.ts +100 -31028
- package/dist/trpc/router.d.ts.map +1 -1
- package/dist/trpc/router.js +12 -3
- package/dist/trpc/router.js.map +1 -1
- package/dist/trpc/routers/app-instances.d.ts +2269 -0
- package/dist/trpc/routers/app-instances.d.ts.map +1 -0
- package/dist/trpc/routers/app-instances.js +52 -0
- package/dist/trpc/routers/app-instances.js.map +1 -0
- package/dist/trpc/routers/app-runs.d.ts +1092 -0
- package/dist/trpc/routers/app-runs.d.ts.map +1 -0
- package/dist/trpc/routers/app-runs.js +28 -0
- package/dist/trpc/routers/app-runs.js.map +1 -0
- package/dist/trpc/routers/applications.d.ts +1095 -0
- package/dist/trpc/routers/applications.d.ts.map +1 -1
- package/dist/trpc/routers/applications.js +5 -2
- package/dist/trpc/routers/applications.js.map +1 -1
- package/dist/trpc/routers/assets.d.ts +10 -0
- package/dist/trpc/routers/assets.d.ts.map +1 -1
- package/dist/trpc/routers/assets.js +4 -0
- package/dist/trpc/routers/assets.js.map +1 -1
- package/dist/trpc/routers/bug-reports.d.ts +33 -0
- package/dist/trpc/routers/bug-reports.d.ts.map +1 -0
- package/dist/trpc/routers/bug-reports.js +69 -0
- package/dist/trpc/routers/bug-reports.js.map +1 -0
- package/dist/trpc/routers/documents.d.ts.map +1 -1
- package/dist/trpc/routers/documents.js +8 -2
- package/dist/trpc/routers/documents.js.map +1 -1
- package/dist/trpc/routers/error-traces.d.ts +132 -0
- package/dist/trpc/routers/error-traces.d.ts.map +1 -0
- package/dist/trpc/routers/error-traces.js +118 -0
- package/dist/trpc/routers/error-traces.js.map +1 -0
- package/dist/trpc/routers/extension.d.ts +3 -3
- package/dist/trpc/routers/extension.js +6 -6
- package/dist/trpc/routers/extension.js.map +1 -1
- package/dist/trpc/routers/gameDraftHistory.d.ts +8 -0
- package/dist/trpc/routers/gameDraftHistory.d.ts.map +1 -0
- package/dist/trpc/routers/gameDraftHistory.js +40 -0
- package/dist/trpc/routers/gameDraftHistory.js.map +1 -0
- package/dist/trpc/routers/games.d.ts +23691 -4799
- package/dist/trpc/routers/games.d.ts.map +1 -1
- package/dist/trpc/routers/games.js +301 -83
- package/dist/trpc/routers/games.js.map +1 -1
- package/dist/trpc/routers/jobs.d.ts +35 -0
- package/dist/trpc/routers/jobs.d.ts.map +1 -1
- package/dist/trpc/routers/jobs.js +84 -1
- package/dist/trpc/routers/jobs.js.map +1 -1
- package/dist/trpc/routers/mcp-config.d.ts +3 -1
- package/dist/trpc/routers/mcp-config.d.ts.map +1 -1
- package/dist/trpc/routers/mcp-config.js +46 -208
- package/dist/trpc/routers/mcp-config.js.map +1 -1
- package/dist/trpc/routers/models.d.ts +130 -0
- package/dist/trpc/routers/models.d.ts.map +1 -1
- package/dist/trpc/routers/models.js +99 -31
- package/dist/trpc/routers/models.js.map +1 -1
- package/dist/trpc/routers/runs.d.ts +472 -0
- package/dist/trpc/routers/runs.d.ts.map +1 -0
- package/dist/trpc/routers/runs.js +40 -0
- package/dist/trpc/routers/runs.js.map +1 -0
- package/dist/trpc/routers/settings.d.ts +3 -0
- package/dist/trpc/routers/settings.d.ts.map +1 -1
- package/dist/trpc/routers/settings.js +2 -0
- package/dist/trpc/routers/settings.js.map +1 -1
- package/dist/trpc/routers/sketch.d.ts +14 -0
- package/dist/trpc/routers/sketch.d.ts.map +1 -1
- package/dist/trpc/routers/storyboards.d.ts +732 -0
- package/dist/trpc/routers/storyboards.d.ts.map +1 -1
- package/dist/trpc/routers/timeline.d.ts +632 -63
- package/dist/trpc/routers/timeline.d.ts.map +1 -1
- package/dist/trpc/routers/timeline.js +178 -2
- package/dist/trpc/routers/timeline.js.map +1 -1
- package/dist/trpc/routers/worker.d.ts +10 -9
- package/dist/trpc/routers/worker.d.ts.map +1 -1
- package/dist/trpc/routers/worker.js +3 -2
- package/dist/trpc/routers/worker.js.map +1 -1
- package/dist/trpc/routers/workflows.d.ts +56 -3
- package/dist/trpc/routers/workflows.d.ts.map +1 -1
- package/dist/trpc/routers/workflows.js +23 -2
- package/dist/trpc/routers/workflows.js.map +1 -1
- package/dist/trpc/sandbox-coverage.d.ts.map +1 -1
- package/dist/trpc/sandbox-coverage.js +90 -0
- package/dist/trpc/sandbox-coverage.js.map +1 -1
- package/dist/websocket-client-session.d.ts +2 -0
- package/dist/websocket-client-session.d.ts.map +1 -1
- package/dist/websocket-client-session.js +14 -0
- package/dist/websocket-client-session.js.map +1 -1
- package/package.json +56 -41
- package/dist/extension-cdp-bridge.d.ts +0 -85
- package/dist/extension-cdp-bridge.d.ts.map +0 -1
- package/dist/extension-cdp-bridge.js +0 -163
- package/dist/extension-cdp-bridge.js.map +0 -1
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { ROOT_CONTEXT, propagation, trace, isSpanContextValid } from "@opentelemetry/api";
|
|
2
|
+
import { getRegisteredRunTrace } from "@nodetool-ai/models";
|
|
3
|
+
import { ensureRunTraceTelemetry, openAppRunTrace, registerWorkflowRunTrace } from "@nodetool-ai/execution";
|
|
1
4
|
/**
|
|
2
5
|
* Everything between "run_job arrived" and "terminal status persisted": the
|
|
3
6
|
* admission gates, the concurrency queue, run start-up, the message stream,
|
|
@@ -9,7 +12,9 @@
|
|
|
9
12
|
* {@link JobExecutionDeps}.
|
|
10
13
|
*/
|
|
11
14
|
import { randomUUID } from "node:crypto";
|
|
15
|
+
import { operationTarget, parseInputStateKey } from "@nodetool-ai/app-runtime";
|
|
12
16
|
import { ApiErrorCode } from "../error-codes.js";
|
|
17
|
+
import { captureError } from "../error-traces.js";
|
|
13
18
|
import { admitSpend, releaseSpend, reserveSpend } from "../credit-gate.js";
|
|
14
19
|
import { JobConcurrencyQueue } from "../job-queue.js";
|
|
15
20
|
import { isBoolean, isFiniteNumber, isFunctionValue, isNonEmptyString, isNumber, isObjectLike, isRecord, isString } from "../lib/wire-values.js";
|
|
@@ -18,17 +23,19 @@ import { ExecutionSession, isExecutionPreflightError, isUnitBilledCapability, pr
|
|
|
18
23
|
import { persistableOutputs, withoutInlineAssetBytes } from "@nodetool-ai/execution/service";
|
|
19
24
|
import { createRunSupervisor } from "../run-supervisor.js";
|
|
20
25
|
import { jobRunRegistry } from "../job-run-registry.js";
|
|
21
|
-
import { Application, Asset, Job, listApplicationVersions, invocationIdInUse, releasedApplicationRelease, releasedApplicationVersion, reserveInvocation, settleInvocation, Workflow } from "@nodetool-ai/models";
|
|
26
|
+
import { Application, AppRunError, claimAppRun, ensureDefaultAppInstance, findAppRunByInvocation, getAppInstance, getAppRun, Asset, Job, listApplicationVersions, invocationIdInUse, releasedApplicationRelease, releasedApplicationVersion, reconcileAppRunCost, reserveInvocation, reserveAppRun, setAppRunInputs, settleAppRun, updateAppRunEstimate, settleInvocation, Workflow } from "@nodetool-ai/models";
|
|
22
27
|
import { getInstanceId } from "../lib/instance-id.js";
|
|
23
28
|
import { requestRemoteJobCancel } from "../job-control.js";
|
|
24
29
|
import { estimateWorkflowCost, nodeExpectedQuantity } from "@nodetool-ai/node-sdk/cost-estimate";
|
|
25
30
|
import { extractPricingParams } from "@nodetool-ai/node-sdk/pricing-params";
|
|
26
31
|
import { getModelUnitPrice } from "@nodetool-ai/model-pricing";
|
|
27
32
|
import { applicationReleaseResponse } from "@nodetool-ai/protocol/api-schemas/applications.js";
|
|
33
|
+
import { appRunSnapshot } from "@nodetool-ai/protocol/api-schemas/app-runs.js";
|
|
28
34
|
import { confineRunRequest, isRunRefusal } from "../lib/app-session-scope.js";
|
|
29
35
|
import { releaseBlockedReason } from "../lib/app-deployment-service.js";
|
|
30
36
|
import { autoSaveAssets, primaryTextOutputName } from "./asset-autosave.js";
|
|
31
37
|
import { createRuntimeContext } from "./model-interfaces.js";
|
|
38
|
+
import { createAppRunFold } from "./app-run-fold.js";
|
|
32
39
|
import { formatSanitizedError, sanitizeLargeText } from "./sanitize.js";
|
|
33
40
|
import { createLogger } from "@nodetool-ai/config";
|
|
34
41
|
const log = createLogger("nodetool.websocket.runner");
|
|
@@ -374,7 +381,19 @@ export class JobExecutionManager {
|
|
|
374
381
|
*/
|
|
375
382
|
async emitBeforeRunFailure(jobId, workflowId, err, persistJob) {
|
|
376
383
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
384
|
+
const appRun = await findAppRunByInvocation(this.session.requireUserId(), jobId);
|
|
385
|
+
if (appRun) {
|
|
386
|
+
await settleAppRun(this.session.requireUserId(), appRun.id, {
|
|
387
|
+
status: "failed",
|
|
388
|
+
error: errorMessage
|
|
389
|
+
});
|
|
390
|
+
}
|
|
377
391
|
this.session.logError("beforeRunJob failed", err);
|
|
392
|
+
captureError(err, {
|
|
393
|
+
source: "job",
|
|
394
|
+
userId: this.session.userId,
|
|
395
|
+
context: { job_id: jobId, workflow_id: workflowId ?? "" }
|
|
396
|
+
});
|
|
378
397
|
await this.session.send({
|
|
379
398
|
type: "job_update",
|
|
380
399
|
status: "failed",
|
|
@@ -589,6 +608,9 @@ export class JobExecutionManager {
|
|
|
589
608
|
* telemetry, which is the same ledger.
|
|
590
609
|
*/
|
|
591
610
|
async admitApplicationRun(req) {
|
|
611
|
+
if (req.app_run_id || this.session.appSession) {
|
|
612
|
+
return this.admitDurableAppRun(req);
|
|
613
|
+
}
|
|
592
614
|
const applicationId = req.application_id;
|
|
593
615
|
if (!applicationId)
|
|
594
616
|
return true;
|
|
@@ -712,6 +734,144 @@ export class JobExecutionManager {
|
|
|
712
734
|
}
|
|
713
735
|
return true;
|
|
714
736
|
}
|
|
737
|
+
async admitDurableAppRun(req) {
|
|
738
|
+
const userId = this.session.requireUserId();
|
|
739
|
+
const jobId = req.job_id ?? randomUUID();
|
|
740
|
+
req.job_id = jobId;
|
|
741
|
+
let claimedRunId = null;
|
|
742
|
+
try {
|
|
743
|
+
if (this.session.appSession && req.application_id) {
|
|
744
|
+
const release = await releasedApplicationRelease(req.application_id, userId);
|
|
745
|
+
if (!release) {
|
|
746
|
+
throw new Error("Application release not found");
|
|
747
|
+
}
|
|
748
|
+
const instance = await ensureDefaultAppInstance({
|
|
749
|
+
userId,
|
|
750
|
+
applicationId: req.application_id,
|
|
751
|
+
sourceId: `public:${req.application_id}:${release.version}`,
|
|
752
|
+
version: release.version,
|
|
753
|
+
name: "Visitor runs",
|
|
754
|
+
snapshot: appRunSnapshot.parse({
|
|
755
|
+
document: release.document,
|
|
756
|
+
workflow_graphs: Object.fromEntries(release.workflows.flatMap((item) => item.graph ? [[item.workflowId, item.graph]] : [])),
|
|
757
|
+
script_documents: {}
|
|
758
|
+
})
|
|
759
|
+
});
|
|
760
|
+
const decision = await reserveAppRun({
|
|
761
|
+
userId,
|
|
762
|
+
instanceId: instance.id,
|
|
763
|
+
invocationId: jobId,
|
|
764
|
+
operationId: req.operation_id ?? "",
|
|
765
|
+
origin: "public",
|
|
766
|
+
estimatedUsd: this.estimateRunCost(req),
|
|
767
|
+
requireFiniteBudget: true
|
|
768
|
+
});
|
|
769
|
+
if (!decision.allowed) {
|
|
770
|
+
return this.refuseRun(req, jobId, ApiErrorCode.BUDGET_EXCEEDED, decision.reason);
|
|
771
|
+
}
|
|
772
|
+
req.app_run_id = decision.run.id;
|
|
773
|
+
req.instance_id = instance.id;
|
|
774
|
+
}
|
|
775
|
+
const run = req.app_run_id
|
|
776
|
+
? await getAppRun(userId, req.app_run_id)
|
|
777
|
+
: null;
|
|
778
|
+
const instance = req.instance_id
|
|
779
|
+
? await getAppInstance(userId, req.instance_id)
|
|
780
|
+
: null;
|
|
781
|
+
if (!run ||
|
|
782
|
+
!instance ||
|
|
783
|
+
run.instance_id !== instance.id ||
|
|
784
|
+
run.invocation_id !== jobId) {
|
|
785
|
+
return this.refuseRun(req, jobId, ApiErrorCode.NOT_FOUND, "App run not found");
|
|
786
|
+
}
|
|
787
|
+
if (run.status !== "running") {
|
|
788
|
+
return this.refuseRun(req, jobId, ApiErrorCode.ALREADY_EXISTS, "App run has already finished");
|
|
789
|
+
}
|
|
790
|
+
const operation = instance.snapshot.document.operations.find((item) => item.id === run.operation_id);
|
|
791
|
+
if (!operation ||
|
|
792
|
+
operationTarget(operation).kind !== "workflow" ||
|
|
793
|
+
(req.operation_id && req.operation_id !== run.operation_id)) {
|
|
794
|
+
return this.refuseRun(req, jobId, ApiErrorCode.INVALID_INPUT, "Operation does not match this run");
|
|
795
|
+
}
|
|
796
|
+
const target = operationTarget(operation);
|
|
797
|
+
if (target.kind !== "workflow" ||
|
|
798
|
+
!target.workflowId ||
|
|
799
|
+
(req.workflow_id && req.workflow_id !== target.workflowId)) {
|
|
800
|
+
return this.refuseRun(req, jobId, ApiErrorCode.INVALID_INPUT, "Workflow does not match this run");
|
|
801
|
+
}
|
|
802
|
+
if (run.origin !== "public") {
|
|
803
|
+
const frozen = run.snapshot?.workflow_graphs[target.workflowId];
|
|
804
|
+
if (!frozen) {
|
|
805
|
+
return this.refuseRun(req, jobId, ApiErrorCode.INVALID_INPUT, "Run has no frozen workflow");
|
|
806
|
+
}
|
|
807
|
+
const overlays = new Map();
|
|
808
|
+
const inputs = instance.variables["__app_inputs"];
|
|
809
|
+
for (const [key, value] of Object.entries(isRecord(inputs) ? inputs : {})) {
|
|
810
|
+
const binding = parseInputStateKey(key);
|
|
811
|
+
if (!binding?.property || binding.operationId !== run.operation_id) {
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
const properties = overlays.get(binding.nodeId) ?? {};
|
|
815
|
+
properties[binding.property] = value;
|
|
816
|
+
overlays.set(binding.nodeId, properties);
|
|
817
|
+
}
|
|
818
|
+
req.graph = {
|
|
819
|
+
...frozen,
|
|
820
|
+
nodes: frozen.nodes.map((node) => {
|
|
821
|
+
const overlay = overlays.get(node.id);
|
|
822
|
+
if (!overlay) {
|
|
823
|
+
return node;
|
|
824
|
+
}
|
|
825
|
+
const properties = isRecord(node.properties)
|
|
826
|
+
? node.properties
|
|
827
|
+
: isRecord(node.data)
|
|
828
|
+
? node.data
|
|
829
|
+
: {};
|
|
830
|
+
return { ...node, properties: { ...properties, ...overlay } };
|
|
831
|
+
})
|
|
832
|
+
};
|
|
833
|
+
await updateAppRunEstimate(userId, run.id, this.estimateRunCost(req));
|
|
834
|
+
}
|
|
835
|
+
if (!(await claimAppRun(userId, run.id, getInstanceId()))) {
|
|
836
|
+
return this.refuseRun(req, jobId, ApiErrorCode.ALREADY_EXISTS, "App run execution already started");
|
|
837
|
+
}
|
|
838
|
+
claimedRunId = run.id;
|
|
839
|
+
await setAppRunInputs(userId, run.id, req.params ?? {});
|
|
840
|
+
req.app_run_id = run.id;
|
|
841
|
+
req.instance_id = instance.id;
|
|
842
|
+
req.user_id = userId;
|
|
843
|
+
req.workflow_id = target.workflowId;
|
|
844
|
+
req.application_id = run.application_id;
|
|
845
|
+
req.application_version = run.version;
|
|
846
|
+
req.operation_id = run.operation_id;
|
|
847
|
+
return true;
|
|
848
|
+
}
|
|
849
|
+
catch (error) {
|
|
850
|
+
if (claimedRunId) {
|
|
851
|
+
try {
|
|
852
|
+
await settleAppRun(userId, claimedRunId, {
|
|
853
|
+
status: "failed",
|
|
854
|
+
actualUsd: 0,
|
|
855
|
+
knownLlmUsd: 0,
|
|
856
|
+
error: error instanceof Error
|
|
857
|
+
? error.message
|
|
858
|
+
: "App run admission failed"
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
catch (settlementError) {
|
|
862
|
+
if (!(settlementError instanceof AppRunError) ||
|
|
863
|
+
settlementError.code !== "not_found") {
|
|
864
|
+
this.session.logError("app run admission settlement failed", settlementError);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
this.session.logError("app run admission failed", error);
|
|
869
|
+
const code = error instanceof AppRunError && error.code === "budget_exceeded"
|
|
870
|
+
? ApiErrorCode.BUDGET_EXCEEDED
|
|
871
|
+
: ApiErrorCode.INVALID_INPUT;
|
|
872
|
+
return this.refuseRun(req, jobId, code, error instanceof Error ? error.message : "App run admission failed");
|
|
873
|
+
}
|
|
874
|
+
}
|
|
715
875
|
/**
|
|
716
876
|
* Gate a run on the user's credit balance — but only the part of the run
|
|
717
877
|
* that spends through NodeTool's managed provider. A graph with no
|
|
@@ -808,8 +968,15 @@ export class JobExecutionManager {
|
|
|
808
968
|
req._accepted_at_ms ??= performance.now();
|
|
809
969
|
if (!(await this.admitApplicationRun(req)))
|
|
810
970
|
return;
|
|
811
|
-
if (!(await this.admitCreditRun(req)))
|
|
971
|
+
if (!(await this.admitCreditRun(req))) {
|
|
972
|
+
if (req.app_run_id) {
|
|
973
|
+
await settleAppRun(this.session.requireUserId(), req.app_run_id, {
|
|
974
|
+
status: "failed",
|
|
975
|
+
error: "Run refused by spending limit"
|
|
976
|
+
});
|
|
977
|
+
}
|
|
812
978
|
return;
|
|
979
|
+
}
|
|
813
980
|
const max = await this.deps.getMaxConcurrentJobs();
|
|
814
981
|
const perWorkflowMax = await this.perWorkflowLimitFor(req);
|
|
815
982
|
// Queue the run when over the global cap, or when this workflow already has
|
|
@@ -949,6 +1116,13 @@ export class JobExecutionManager {
|
|
|
949
1116
|
try {
|
|
950
1117
|
await this.startJobInner(req, releaseSlot);
|
|
951
1118
|
}
|
|
1119
|
+
catch (error) {
|
|
1120
|
+
if (req.app_run_id) {
|
|
1121
|
+
await this.emitBeforeRunFailure(req.job_id ?? "", req.workflow_id ?? null, error, true);
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
throw error;
|
|
1125
|
+
}
|
|
952
1126
|
finally {
|
|
953
1127
|
// Safety net: if startJobInner returned/threw without registering, the
|
|
954
1128
|
// slot is freed so it doesn't leak and permanently shrink the cap.
|
|
@@ -1011,11 +1185,23 @@ export class JobExecutionManager {
|
|
|
1011
1185
|
const workspace = this.session.workspaceResolver
|
|
1012
1186
|
? await this.session.workspaceResolver(workflowId ?? null, userId)
|
|
1013
1187
|
: null;
|
|
1188
|
+
const appRun = req.app_run_id
|
|
1189
|
+
? await getAppRun(userId, req.app_run_id)
|
|
1190
|
+
: null;
|
|
1014
1191
|
const context = createRuntimeContext({
|
|
1015
1192
|
jobId,
|
|
1016
1193
|
workflowId,
|
|
1017
1194
|
projectId,
|
|
1018
1195
|
userId,
|
|
1196
|
+
...(appRun && {
|
|
1197
|
+
appRunContext: {
|
|
1198
|
+
userId,
|
|
1199
|
+
appRunId: appRun.id,
|
|
1200
|
+
instanceId: appRun.instance_id,
|
|
1201
|
+
traceId: appRun.trace_id,
|
|
1202
|
+
origin: appRun.origin
|
|
1203
|
+
}
|
|
1204
|
+
}),
|
|
1019
1205
|
workspace,
|
|
1020
1206
|
assetOutputMode: this.session.mode === "text" ? "data_uri" : "temp_url",
|
|
1021
1207
|
persistOutputAssets: executionOptions.assetPersistence === "auto"
|
|
@@ -1044,6 +1230,9 @@ export class JobExecutionManager {
|
|
|
1044
1230
|
const existing = await Job.get(jobId);
|
|
1045
1231
|
if (existing) {
|
|
1046
1232
|
if (existing.status === "cancelled") {
|
|
1233
|
+
if (appRun) {
|
|
1234
|
+
await settleAppRun(userId, appRun.id, { status: "cancelled" });
|
|
1235
|
+
}
|
|
1047
1236
|
log.info("Skipping start of cancelled job", { jobId });
|
|
1048
1237
|
// Nothing was registered in activeJobs yet — free the reserved
|
|
1049
1238
|
// slot and promote any queued run, matching every other slot
|
|
@@ -1134,11 +1323,27 @@ export class JobExecutionManager {
|
|
|
1134
1323
|
// uses: a bare throw reaches handleCommand as a generic `invalid_command`
|
|
1135
1324
|
// the UI never associates with the job, so the run appears to spin
|
|
1136
1325
|
// forever instead of failing with the reason.
|
|
1326
|
+
let finishAppTrace;
|
|
1327
|
+
try {
|
|
1328
|
+
await ensureRunTraceTelemetry();
|
|
1329
|
+
const incomingParent = req.traceparent ? trace.getSpanContext(propagation.extract(ROOT_CONTEXT, { traceparent: req.traceparent })) : undefined;
|
|
1330
|
+
const acceptedParent = incomingParent && isSpanContextValid(incomingParent) && !this.session.appSession && appRun && incomingParent.traceId === appRun.trace_id && await getRegisteredRunTrace(userId, incomingParent.traceId, appRun.id) ? incomingParent : undefined;
|
|
1331
|
+
finishAppTrace = appRun ? await openAppRunTrace(context, appRun, acceptedParent) : undefined;
|
|
1332
|
+
if (executionOptions.persistence === "job") {
|
|
1333
|
+
await registerWorkflowRunTrace(context, { jobId, workflowId: workflowId ?? null, inlineGraph: Boolean(req.graph), origin: this.session.appSession ? "public" : "ui" });
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
catch (error) {
|
|
1337
|
+
await finishAppTrace?.("failed", error instanceof Error ? error.message : String(error));
|
|
1338
|
+
await this.emitBeforeRunFailure(jobId, workflowId, error, executionOptions.persistence === "job");
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
1137
1341
|
let session;
|
|
1138
1342
|
try {
|
|
1139
1343
|
session = await ExecutionSession.create(sessionOptions);
|
|
1140
1344
|
}
|
|
1141
1345
|
catch (err) {
|
|
1346
|
+
await finishAppTrace?.("failed", err instanceof Error ? err.message : String(err));
|
|
1142
1347
|
if (!isExecutionPreflightError(err))
|
|
1143
1348
|
throw err;
|
|
1144
1349
|
await this.emitBeforeRunFailure(jobId, workflowId, err, executionOptions.persistence === "job");
|
|
@@ -1163,7 +1368,9 @@ export class JobExecutionManager {
|
|
|
1163
1368
|
persistenceMs,
|
|
1164
1369
|
kernelStartedAt: performance.now()
|
|
1165
1370
|
},
|
|
1166
|
-
applicationId: req.application_id ?? null
|
|
1371
|
+
applicationId: req.application_id ?? null,
|
|
1372
|
+
...(appRun && { appRunFold: createAppRunFold(appRun) }),
|
|
1373
|
+
...(finishAppTrace && { finishAppTrace })
|
|
1167
1374
|
};
|
|
1168
1375
|
// Decouple the run from this socket: from here on every frame carrying
|
|
1169
1376
|
// this job_id is stamped with `job_seq` and buffered, so a client that
|
|
@@ -1304,6 +1511,16 @@ export class JobExecutionManager {
|
|
|
1304
1511
|
}
|
|
1305
1512
|
else if (active.status === "failed") {
|
|
1306
1513
|
job.markFailed(active.error ?? "Unknown error");
|
|
1514
|
+
// The run's error is a string by now. A stack built here would
|
|
1515
|
+
// point at this method, not at the failure, so none is sent.
|
|
1516
|
+
captureError(active.error ?? "Unknown error", {
|
|
1517
|
+
source: "job",
|
|
1518
|
+
userId: job.user_id,
|
|
1519
|
+
context: {
|
|
1520
|
+
job_id: active.jobId,
|
|
1521
|
+
workflow_id: active.workflowId ?? ""
|
|
1522
|
+
}
|
|
1523
|
+
});
|
|
1307
1524
|
}
|
|
1308
1525
|
else if (active.status === "cancelled") {
|
|
1309
1526
|
job.markCancelled();
|
|
@@ -1327,6 +1544,38 @@ export class JobExecutionManager {
|
|
|
1327
1544
|
* the estimate standing rather than handing the spend back. Never throws.
|
|
1328
1545
|
*/
|
|
1329
1546
|
async settleApplicationInvocation(active) {
|
|
1547
|
+
await active.finishAppTrace?.(active.status === "failed" ? "failed" : active.status === "cancelled" ? "cancelled" : "completed", active.error, this.runMeasuredCost(active));
|
|
1548
|
+
const identity = active.context.appRunContext;
|
|
1549
|
+
if (identity) {
|
|
1550
|
+
try {
|
|
1551
|
+
const settlement = {
|
|
1552
|
+
status: active.status === "failed"
|
|
1553
|
+
? "failed"
|
|
1554
|
+
: active.status === "cancelled"
|
|
1555
|
+
? "cancelled"
|
|
1556
|
+
: "completed",
|
|
1557
|
+
error: active.context.runTraceContext?.policy.contentSuppressed && active.error ? "Execution failed" : active.error ?? null,
|
|
1558
|
+
actualUsd: this.runMeasuredCost(active),
|
|
1559
|
+
outputs: active.appRunFold?.outputs() ?? {},
|
|
1560
|
+
documents: active.context.getAppRunDocuments(),
|
|
1561
|
+
contentSuppressed: active.context.runTraceContext?.policy.contentSuppressed,
|
|
1562
|
+
secretValues: [...active.context.getResolvedSecretValues()]
|
|
1563
|
+
};
|
|
1564
|
+
const llmCost = active.context.getAppRunLlmCost();
|
|
1565
|
+
if (llmCost !== null) {
|
|
1566
|
+
settlement.knownLlmUsd = llmCost;
|
|
1567
|
+
}
|
|
1568
|
+
await settleAppRun(identity.userId, identity.appRunId, settlement);
|
|
1569
|
+
await reconcileAppRunCost(identity.userId, identity.appRunId);
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
catch (error) {
|
|
1573
|
+
if (!(error instanceof AppRunError) || error.code !== "not_found") {
|
|
1574
|
+
this.session.logError("app run settlement failed", error);
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1330
1579
|
if (!active.applicationId)
|
|
1331
1580
|
return;
|
|
1332
1581
|
try {
|
|
@@ -1587,18 +1836,25 @@ export class JobExecutionManager {
|
|
|
1587
1836
|
continue;
|
|
1588
1837
|
}
|
|
1589
1838
|
const status = outbound.type === "job_update" ? String(outbound.status ?? "") : "";
|
|
1590
|
-
const suppressProvisionalCompletion = active.
|
|
1591
|
-
status
|
|
1592
|
-
|
|
1839
|
+
const suppressProvisionalCompletion = (Boolean(active.context.appRunContext) &&
|
|
1840
|
+
TERMINAL_JOB_STATUSES.includes(status)) ||
|
|
1841
|
+
(active.requireTerminalResult &&
|
|
1842
|
+
status === "completed" &&
|
|
1843
|
+
outbound.result === undefined);
|
|
1593
1844
|
if (!suppressProvisionalCompletion) {
|
|
1594
1845
|
if (outbound.type === "job_update") {
|
|
1595
1846
|
if (status === "running") {
|
|
1596
1847
|
runningSeen = true;
|
|
1597
1848
|
}
|
|
1598
1849
|
else if (TERMINAL_JOB_STATUSES.includes(status)) {
|
|
1850
|
+
if (active.context.appRunContext) {
|
|
1851
|
+
await executionSettled;
|
|
1852
|
+
await this.settleApplicationInvocation(active);
|
|
1853
|
+
}
|
|
1599
1854
|
await ensureRunningFrame();
|
|
1600
1855
|
}
|
|
1601
1856
|
}
|
|
1857
|
+
active.appRunFold?.fold(outbound);
|
|
1602
1858
|
await this.session.send(outbound);
|
|
1603
1859
|
}
|
|
1604
1860
|
if (outbound.type === "job_update" && !suppressProvisionalCompletion) {
|
|
@@ -1620,12 +1876,30 @@ export class JobExecutionManager {
|
|
|
1620
1876
|
if (Object.keys(finalOutputs).length > 0) {
|
|
1621
1877
|
finalOutputs = await this.normalizeFinalOutputs(active, finalOutputs);
|
|
1622
1878
|
}
|
|
1879
|
+
if (active.appRunFold) {
|
|
1880
|
+
for (const [key, values] of Object.entries(finalOutputs)) {
|
|
1881
|
+
const nodeId = this.resolveOutputNodeForKey(active, key)?.id ?? key;
|
|
1882
|
+
for (const [index, value] of values.entries()) {
|
|
1883
|
+
active.appRunFold.fold({
|
|
1884
|
+
type: "output_update",
|
|
1885
|
+
job_id: active.jobId,
|
|
1886
|
+
node_id: nodeId,
|
|
1887
|
+
value,
|
|
1888
|
+
disposition: index === 0 ? "replace" : "append",
|
|
1889
|
+
done: true
|
|
1890
|
+
});
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1623
1894
|
if (active.executionOptions.eventDetail !== "terminal" &&
|
|
1624
1895
|
!outputUpdateSeen &&
|
|
1625
1896
|
Object.keys(finalOutputs).length > 0) {
|
|
1626
1897
|
await this.sendOutputUpdates(active, finalOutputs);
|
|
1627
1898
|
}
|
|
1628
1899
|
const relayCompletedAt = performance.now();
|
|
1900
|
+
if (active.context.appRunContext) {
|
|
1901
|
+
await this.settleApplicationInvocation(active);
|
|
1902
|
+
}
|
|
1629
1903
|
if (!terminalSeen ||
|
|
1630
1904
|
(!terminalWithResultSeen && Object.keys(finalOutputs).length > 0)) {
|
|
1631
1905
|
await ensureRunningFrame();
|
|
@@ -1706,7 +1980,7 @@ export class JobExecutionManager {
|
|
|
1706
1980
|
// indistinguishable from a missing one — it must be neither reported
|
|
1707
1981
|
// nor settled below.
|
|
1708
1982
|
const row = (await Job.get(jobId));
|
|
1709
|
-
const job = row && row.user_id ===
|
|
1983
|
+
const job = row && row.user_id === this.session.requireUserId() ? row : null;
|
|
1710
1984
|
const settled = job != null &&
|
|
1711
1985
|
(job.status === "completed" ||
|
|
1712
1986
|
job.status === "failed" ||
|
|
@@ -1781,6 +2055,11 @@ export class JobExecutionManager {
|
|
|
1781
2055
|
// and tell the client it's cancelled before it ever starts.
|
|
1782
2056
|
const queued = this.jobQueue.remove(jobId);
|
|
1783
2057
|
if (queued) {
|
|
2058
|
+
if (queued.app_run_id) {
|
|
2059
|
+
await settleAppRun(this.session.requireUserId(), queued.app_run_id, {
|
|
2060
|
+
status: "cancelled"
|
|
2061
|
+
});
|
|
2062
|
+
}
|
|
1784
2063
|
releaseSpend(this.session.requireUserId(), jobId);
|
|
1785
2064
|
const cancelledWorkflowId = queued.workflow_id ?? workflowId ?? null;
|
|
1786
2065
|
// Mark the persisted queued row cancelled so it leaves the queue in
|
|
@@ -2062,6 +2341,11 @@ export class JobExecutionManager {
|
|
|
2062
2341
|
if (!queuedId)
|
|
2063
2342
|
continue;
|
|
2064
2343
|
try {
|
|
2344
|
+
if (queued.app_run_id) {
|
|
2345
|
+
await settleAppRun(this.session.requireUserId(), queued.app_run_id, {
|
|
2346
|
+
status: "cancelled"
|
|
2347
|
+
});
|
|
2348
|
+
}
|
|
2065
2349
|
const job = await Job.get(queuedId);
|
|
2066
2350
|
if (job) {
|
|
2067
2351
|
job.markCancelled();
|
|
@@ -2074,6 +2358,12 @@ export class JobExecutionManager {
|
|
|
2074
2358
|
}
|
|
2075
2359
|
for (const dequeuedId of this.dequeuedJobs) {
|
|
2076
2360
|
try {
|
|
2361
|
+
const run = await findAppRunByInvocation(this.session.requireUserId(), dequeuedId);
|
|
2362
|
+
if (run) {
|
|
2363
|
+
await settleAppRun(this.session.requireUserId(), run.id, {
|
|
2364
|
+
status: "cancelled"
|
|
2365
|
+
});
|
|
2366
|
+
}
|
|
2077
2367
|
const job = await Job.get(dequeuedId);
|
|
2078
2368
|
if (job) {
|
|
2079
2369
|
job.markCancelled();
|