taskmeld 0.1.2 → 0.1.41
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/README.md +176 -176
- package/README.zh-CN.md +176 -176
- package/dist/src/app/app-context-env.js +1 -1
- package/dist/src/app/create-app-context.js +3 -3
- package/dist/src/app/data-dir.js +13 -3
- package/dist/src/app/pipeline-config.js +4 -4
- package/dist/src/app/pipeline-registry.js +11 -11
- package/dist/src/app/pipeline-runtime.js +6 -9
- package/dist/src/app/runtime-store.js +3 -3
- package/dist/src/artifacts/artifact-cleanup.js +17 -17
- package/dist/src/artifacts/artifact-index.js +14 -14
- package/dist/src/artifacts/artifact-rebuilder.js +3 -3
- package/dist/src/artifacts/storage-service.js +18 -18
- package/dist/src/cli/bootstrap.js +7 -7
- package/dist/src/cli/commands/agent.js +12 -11
- package/dist/src/cli/commands/artifact.js +31 -30
- package/dist/src/cli/commands/init.js +49 -47
- package/dist/src/cli/commands/pipeline/result.js +9 -8
- package/dist/src/cli/commands/pipeline/selector.js +1 -1
- package/dist/src/cli/commands/pipeline/watch.js +2 -2
- package/dist/src/cli/commands/pipeline.js +54 -53
- package/dist/src/cli/commands/scheduler.js +9 -8
- package/dist/src/cli/commands/server.js +12 -11
- package/dist/src/cli/commands/system.js +4 -3
- package/dist/src/cli/errors.js +2 -2
- package/dist/src/cli/help.js +18 -17
- package/dist/src/cli/i18n.js +46 -0
- package/dist/src/cli/locales/en.json +244 -0
- package/dist/src/cli/locales/zh.json +244 -0
- package/dist/src/cli/output.js +3 -3
- package/dist/src/cli/renderers/engine/markdown.js +1 -1
- package/dist/src/cli/renderers/specs/index.js +1 -1
- package/dist/src/cli/router.js +1 -1
- package/dist/src/cli/server-runtime-client.js +54 -95
- package/dist/src/cli/ui-prompts.js +96 -0
- package/dist/src/cli/ws-runtime-client.js +51 -0
- package/dist/src/gateway/gateway-client.js +4 -4
- package/dist/src/index.js +28 -2
- package/dist/src/logs/run-log-reader.js +1 -1
- package/dist/src/pipeline/agent-activity.js +2 -2
- package/dist/src/pipeline/artifact-storage.js +11 -11
- package/dist/src/pipeline/diagnostics/dependency-diagnostic.js +11 -11
- package/dist/src/pipeline/dispatch/pipeline-inbound-queue.js +2 -2
- package/dist/src/pipeline/execution/group-item-executor.js +1 -1
- package/dist/src/pipeline/execution/node-item-executor.js +3 -3
- package/dist/src/pipeline/execution/node-runner.js +7 -7
- package/dist/src/pipeline/execution/readiness-state.js +1 -1
- package/dist/src/pipeline/execution/reject-handler.js +5 -5
- package/dist/src/pipeline/execution/rejected-artifact-archiver.js +1 -1
- package/dist/src/pipeline/execution/route-item-manager.js +4 -4
- package/dist/src/pipeline/execution/run-abort-controller.js +5 -5
- package/dist/src/pipeline/execution/run-state-helpers.js +2 -2
- package/dist/src/pipeline/execution/service.js +4 -4
- package/dist/src/pipeline/execution/structured-node-runner.js +24 -24
- package/dist/src/pipeline/execution-timeout.js +3 -3
- package/dist/src/pipeline/identity/index.js +3 -3
- package/dist/src/pipeline/item-batch-controller.js +6 -6
- package/dist/src/pipeline/scheduler/dependency-state.js +5 -5
- package/dist/src/pipeline/scheduler-service.js +24 -24
- package/dist/src/pipeline/state-machine.js +2 -2
- package/dist/src/pipeline/structured-output/contract.js +4 -4
- package/dist/src/pipeline/structured-output/index.js +2 -2
- package/dist/src/pipeline/structured-output/parser.js +5 -5
- package/dist/src/pipeline/structured-output/prompt.js +38 -38
- package/dist/src/pipeline/structured-output/waiter.js +6 -6
- package/dist/src/pipeline/template.js +5 -5
- package/dist/src/pipeline/timeline-log-store.js +5 -5
- package/dist/src/pipeline/tool-activity.js +3 -3
- package/dist/src/pipeline/types/pipeline-output.js +1 -1
- package/dist/src/pipeline/workflow/branch-rules.js +19 -19
- package/dist/src/pipeline/workflow/io.js +1 -1
- package/dist/src/pipeline/workflow/normalize.js +18 -18
- package/dist/src/pipeline/workflow/template-mapper.js +3 -3
- package/dist/src/pipeline/workflow/validate.js +39 -39
- package/dist/src/pipeline/workflow-graph.js +10 -10
- package/dist/src/server/http-handler.js +74 -0
- package/dist/src/services/agent-service.js +2 -2
- package/dist/src/services/gateway-read-helpers.js +1 -1
- package/dist/src/services/pipeline-service.js +19 -19
- package/dist/src/services/pipeline-status.js +4 -4
- package/dist/src/services/read-services.js +1 -1
- package/dist/src/services/session-service.js +6 -6
- package/dist/src/services/system-service.js +1 -1
- package/dist/src/transport/ws-broker.js +12 -1
- package/dist/src/transport/ws-handler.js +60 -0
- package/dist/src/transport/ws-methods/agents.js +144 -0
- package/dist/src/transport/ws-methods/artifacts.js +171 -0
- package/dist/src/transport/ws-methods/gateway.js +16 -0
- package/dist/src/transport/ws-methods/logs.js +43 -0
- package/dist/src/transport/ws-methods/pipeline-batch.js +68 -0
- package/dist/src/transport/ws-methods/pipeline-links.js +100 -0
- package/dist/src/transport/ws-methods/pipeline-queue.js +51 -0
- package/dist/src/transport/ws-methods/pipeline-runtime.js +151 -0
- package/dist/src/transport/ws-methods/pipeline-scheduler.js +48 -0
- package/dist/src/transport/ws-methods/pipeline-workflow.js +127 -0
- package/dist/src/transport/ws-methods/pipelines.js +56 -0
- package/dist/src/transport/ws-methods/register-all.js +32 -0
- package/dist/src/transport/ws-methods/sessions.js +154 -0
- package/dist/src/transport/ws-methods/timeline.js +10 -0
- package/dist/src/{server/routes/pipeline-identity.js → transport/ws-methods/utils.js} +14 -9
- package/dist/src/version.js +1 -1
- package/package.json +15 -7
- package/web/dist/assets/agent-DP6TMcLj.js +1 -0
- package/web/dist/assets/agent-DmJHzLyj.js +1 -0
- package/web/dist/assets/artifact-BqnoZy2M.js +1 -0
- package/web/dist/assets/artifact-DfDkgkno.js +1 -0
- package/web/dist/assets/common-DRMTVwE9.js +1 -0
- package/web/dist/assets/common-DeXccbr2.js +1 -0
- package/web/dist/assets/dispatch-CBskGCQI.js +1 -0
- package/web/dist/assets/dispatch-sk4Wp30e.js +1 -0
- package/web/dist/assets/index-C8wTjZvH.css +1 -0
- package/web/dist/assets/index-DYDQZRLk.js +58 -0
- package/web/dist/assets/log-DN8cjb0w.js +1 -0
- package/web/dist/assets/log-HSeA_dYy.js +1 -0
- package/web/dist/assets/modal-BdNai9jf.js +1 -0
- package/web/dist/assets/modal-D9_KDpFD.js +1 -0
- package/web/dist/assets/nav-BmF7oAKg.js +1 -0
- package/web/dist/assets/nav-IjC2xqXQ.js +1 -0
- package/web/dist/assets/node-detail-CENRXcrh.js +1 -0
- package/web/dist/assets/node-detail-bndPr0IM.js +1 -0
- package/web/dist/assets/overview-B87zWAxq.js +1 -0
- package/web/dist/assets/overview-gQvk-NOK.js +1 -0
- package/web/dist/assets/pipeline-D4dSJRDz.js +1 -0
- package/web/dist/assets/pipeline-DZzyOqQa.js +1 -0
- package/web/dist/assets/session-CUWvU14v.js +5 -0
- package/web/dist/assets/session-DQ6UuCaJ.js +5 -0
- package/web/dist/assets/timeline-8y_2_0Em.js +1 -0
- package/web/dist/assets/timeline-CAPsXUTC.js +1 -0
- package/web/dist/index.html +3 -3
- package/dist/src/app/pipeline-plugin-config.js +0 -2
- package/dist/src/server/api-handler.js +0 -163
- package/dist/src/server/http-utils.js +0 -34
- package/dist/src/server/middleware.js +0 -61
- package/dist/src/server/router.js +0 -105
- package/dist/src/server/routes/agents.js +0 -189
- package/dist/src/server/routes/artifacts.js +0 -163
- package/dist/src/server/routes/gateway.js +0 -18
- package/dist/src/server/routes/health.js +0 -16
- package/dist/src/server/routes/logs.js +0 -73
- package/dist/src/server/routes/pipeline-batch.js +0 -163
- package/dist/src/server/routes/pipeline-diagnostics.js +0 -33
- package/dist/src/server/routes/pipeline-links.js +0 -117
- package/dist/src/server/routes/pipeline-outputs.js +0 -27
- package/dist/src/server/routes/pipeline-queue.js +0 -62
- package/dist/src/server/routes/pipeline-runtime.js +0 -162
- package/dist/src/server/routes/pipeline-scheduler.js +0 -69
- package/dist/src/server/routes/pipeline-workflow.js +0 -180
- package/dist/src/server/routes/pipelines.js +0 -96
- package/dist/src/server/routes/sessions.js +0 -244
- package/dist/src/server/routes/timeline.js +0 -14
- package/dist/src/server/serve-static.js +0 -42
- package/web/dist/assets/index-CWnfhkn-.js +0 -65
- package/web/dist/assets/index-gZ0xOfSO.css +0 -1
- /package/dist/src/{server → transport/ws-methods}/types.js +0 -0
|
@@ -8,9 +8,9 @@ const run_state_helpers_1 = require("./execution/run-state-helpers");
|
|
|
8
8
|
const state_1 = require("./state");
|
|
9
9
|
const identity_1 = require("./identity");
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* Pipeline scheduler.
|
|
12
|
+
* Responsible for: deciding when to execute nodes, managing concurrency, controlling scheduling mode.
|
|
13
|
+
* Not responsible for: the specific execution logic of nodes.
|
|
14
14
|
*/
|
|
15
15
|
const createSchedulerService = (deps) => {
|
|
16
16
|
const isSchedulerPluginEnabled = () => deps.graph.getWorkflow().plugins.scheduler.enabled;
|
|
@@ -96,8 +96,8 @@ const createSchedulerService = (deps) => {
|
|
|
96
96
|
currentGroupItem.attempt = 0;
|
|
97
97
|
currentGroupItem.artifacts = [];
|
|
98
98
|
}
|
|
99
|
-
//
|
|
100
|
-
//
|
|
99
|
+
// In manual retry mode, the target node may already have its dependencies met but was reset to blocked.
|
|
100
|
+
// Re-evaluate dependencies first, then decide whether to execute the first node immediately, to avoid reliably reproducing node_retry_blocked.
|
|
101
101
|
markReadyItemsFromDependencies();
|
|
102
102
|
markReadyGroupsFromDependencies();
|
|
103
103
|
deps.runtimeStore.emitPipeline();
|
|
@@ -121,10 +121,10 @@ const createSchedulerService = (deps) => {
|
|
|
121
121
|
return { ok: true, node, drained };
|
|
122
122
|
};
|
|
123
123
|
/**
|
|
124
|
-
*
|
|
124
|
+
* Core scheduling loop. Iterates over ready nodes and executes them. This is the scheduler's core responsibility.
|
|
125
125
|
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
126
|
+
* The passed AbortSignal is only used to exit the local drain loop early;
|
|
127
|
+
* remote agent stopping is handled by executionService.abortRunControllers via the "/stop" command.
|
|
128
128
|
*/
|
|
129
129
|
const drainPipeline = async (reason, signal) => {
|
|
130
130
|
const manualTick = reason.startsWith("manual_tick");
|
|
@@ -141,7 +141,7 @@ const createSchedulerService = (deps) => {
|
|
|
141
141
|
return { executed: 0, hardFailed: false };
|
|
142
142
|
}
|
|
143
143
|
if (drainInFlight) {
|
|
144
|
-
deps.runtimeStore.pushTimeline(`[
|
|
144
|
+
deps.runtimeStore.pushTimeline(`[Scheduling drain lock] caller=${reason} triggered drain, but ${drainInFlightReason} drain is already in progress, merged into existing drain`, "info");
|
|
145
145
|
return drainInFlight;
|
|
146
146
|
}
|
|
147
147
|
drainInFlight = (async () => {
|
|
@@ -161,13 +161,13 @@ const createSchedulerService = (deps) => {
|
|
|
161
161
|
active.add(task);
|
|
162
162
|
};
|
|
163
163
|
while (true) {
|
|
164
|
-
//
|
|
165
|
-
//
|
|
164
|
+
// Client-side abort: after external call to abortRunControllers, the signal becomes aborted,
|
|
165
|
+
// the drain loop exits early and no longer waits for active nodes to finish naturally.
|
|
166
166
|
if (signal?.aborted) {
|
|
167
167
|
stopScheduling = true;
|
|
168
168
|
}
|
|
169
169
|
if (executed >= maxIterations) {
|
|
170
|
-
deps.runtimeStore.pushTimeline(
|
|
170
|
+
deps.runtimeStore.pushTimeline(`Scheduling reached global iteration limit: ${maxIterations}`, "warn");
|
|
171
171
|
break;
|
|
172
172
|
}
|
|
173
173
|
while (!stopScheduling && !signal?.aborted && active.size < maxConcurrency && executed < maxIterations) {
|
|
@@ -179,7 +179,7 @@ const createSchedulerService = (deps) => {
|
|
|
179
179
|
const batchLabel = batch
|
|
180
180
|
.map((item) => ("nodeId" in item ? `${item.nodeId}#${item.itemKey}` : `group:${item.groupId}#${item.itemKey}`))
|
|
181
181
|
.join(", ");
|
|
182
|
-
deps.runtimeStore.pushTimeline(
|
|
182
|
+
deps.runtimeStore.pushTimeline(`Pipeline auto-scheduled: ${batchLabel} (${reason})`);
|
|
183
183
|
for (const item of batch) {
|
|
184
184
|
launchItem(item);
|
|
185
185
|
}
|
|
@@ -219,8 +219,8 @@ const createSchedulerService = (deps) => {
|
|
|
219
219
|
return drainInFlight;
|
|
220
220
|
};
|
|
221
221
|
/**
|
|
222
|
-
*
|
|
223
|
-
*
|
|
222
|
+
* Batch run controller. Manages the lifecycle of keyword-pool batch execution (start, stop, cancel, status query).
|
|
223
|
+
* Belongs to the scheduler's responsibility — controls when and how to execute batches.
|
|
224
224
|
*/
|
|
225
225
|
const itemBatchController = (0, item_batch_controller_1.createItemBatchController)({
|
|
226
226
|
pipelineId: deps.pipelineId,
|
|
@@ -231,7 +231,7 @@ const createSchedulerService = (deps) => {
|
|
|
231
231
|
deps.executionService.setActiveBatchKeywordItems([...batchItems]);
|
|
232
232
|
deps.graph.syncRunGroupsFromWorkflow(nextRun);
|
|
233
233
|
deps.runtimeStore.emitPipeline();
|
|
234
|
-
deps.runtimeStore.pushTimeline(
|
|
234
|
+
deps.runtimeStore.pushTimeline(`Batch run started: batch ${batchIndex}/${totalBatches}, keywords ${batchItems.length}/${totalItems}`);
|
|
235
235
|
let drained;
|
|
236
236
|
try {
|
|
237
237
|
const drainSignal = deps.executionService.getOrCreateDrainSignal(getRun().id);
|
|
@@ -243,7 +243,7 @@ const createSchedulerService = (deps) => {
|
|
|
243
243
|
deps.executionService.setActiveBatchKeywordItems(null);
|
|
244
244
|
}
|
|
245
245
|
if (getRun().status === "running") {
|
|
246
|
-
deps.runtimeStore.pushTimeline(
|
|
246
|
+
deps.runtimeStore.pushTimeline(`Batch run ended: batch ${batchIndex} has no further executable nodes, proceeding to next batch`, "warn", {
|
|
247
247
|
batchIndex,
|
|
248
248
|
totalBatches,
|
|
249
249
|
drained,
|
|
@@ -251,9 +251,9 @@ const createSchedulerService = (deps) => {
|
|
|
251
251
|
});
|
|
252
252
|
return { ok: true };
|
|
253
253
|
}
|
|
254
|
-
//
|
|
254
|
+
// Only stop subsequent batches on hard-failure scenarios; business-type failed (status=failed) allows continuing to the next batch.
|
|
255
255
|
if (drained.hardFailed) {
|
|
256
|
-
deps.runtimeStore.pushTimeline(
|
|
256
|
+
deps.runtimeStore.pushTimeline(`Batch run failed: batch ${batchIndex}, subsequent batches stopped`, "error", {
|
|
257
257
|
batchIndex,
|
|
258
258
|
totalBatches,
|
|
259
259
|
drained,
|
|
@@ -261,14 +261,14 @@ const createSchedulerService = (deps) => {
|
|
|
261
261
|
});
|
|
262
262
|
return { ok: false, error: `batch_${batchIndex}_failed`, hardStop: true };
|
|
263
263
|
}
|
|
264
|
-
deps.runtimeStore.pushTimeline(
|
|
264
|
+
deps.runtimeStore.pushTimeline(`Batch run completed: batch ${batchIndex}/${totalBatches}, run=${getRun().id}`);
|
|
265
265
|
return { ok: true };
|
|
266
266
|
},
|
|
267
267
|
});
|
|
268
268
|
return {
|
|
269
269
|
getSchedulerState: () => ({
|
|
270
270
|
...schedulerState,
|
|
271
|
-
//
|
|
271
|
+
// When the scheduler plugin is disabled, externally present as disabled to avoid UI/runtime state inconsistency.
|
|
272
272
|
enabled: isSchedulerPluginEnabled() && schedulerState.enabled,
|
|
273
273
|
}),
|
|
274
274
|
setSchedulerEnabled: (enabled) => {
|
|
@@ -285,21 +285,21 @@ const createSchedulerService = (deps) => {
|
|
|
285
285
|
startBatchRun: (items, batchSize, options) => {
|
|
286
286
|
const started = itemBatchController.start(items, batchSize, options);
|
|
287
287
|
if (started.ok) {
|
|
288
|
-
deps.runtimeStore.pushTimeline(
|
|
288
|
+
deps.runtimeStore.pushTimeline(`Batch run started: ${started.snapshot.totalItems} total items, ${started.snapshot.batchSize} per batch`);
|
|
289
289
|
}
|
|
290
290
|
return started;
|
|
291
291
|
},
|
|
292
292
|
stopBatchRun: () => {
|
|
293
293
|
const stopped = itemBatchController.stop();
|
|
294
294
|
if (stopped.ok) {
|
|
295
|
-
deps.runtimeStore.pushTimeline("
|
|
295
|
+
deps.runtimeStore.pushTimeline("Batch run stop requested (takes effect after current batch completes)", "warn");
|
|
296
296
|
}
|
|
297
297
|
return stopped;
|
|
298
298
|
},
|
|
299
299
|
cancelBatchRun: () => {
|
|
300
300
|
const canceled = itemBatchController.cancel();
|
|
301
301
|
if (canceled.ok) {
|
|
302
|
-
deps.runtimeStore.pushTimeline("
|
|
302
|
+
deps.runtimeStore.pushTimeline("Batch run cancelled immediately (plugin disabled)", "warn");
|
|
303
303
|
}
|
|
304
304
|
return canceled;
|
|
305
305
|
},
|
|
@@ -83,8 +83,8 @@ const transitionStatus = (current, next, command) => {
|
|
|
83
83
|
if (current === next)
|
|
84
84
|
return current;
|
|
85
85
|
const allowedByCommand = exports.VALID_TRANSITIONS[current];
|
|
86
|
-
//
|
|
87
|
-
//
|
|
86
|
+
// When no command is specified, only execute + dependency permissions are allowed (safe default),
|
|
87
|
+
// special permissions (route_backfill/retry_reset/reject_reset/sleep/group_aggregate) require an explicit command.
|
|
88
88
|
const allowed = command
|
|
89
89
|
? (allowedByCommand[command] ?? [])
|
|
90
90
|
: [
|
|
@@ -12,7 +12,7 @@ const normalizeSchemaVersion = (value) => {
|
|
|
12
12
|
if (typeof value !== "string")
|
|
13
13
|
return null;
|
|
14
14
|
const trimmed = value.trim();
|
|
15
|
-
//
|
|
15
|
+
// Accommodate models that output schemaVersion as a numeric string (e.g. "1").
|
|
16
16
|
if (!/^\d+$/.test(trimmed))
|
|
17
17
|
return null;
|
|
18
18
|
const parsed = Number(trimmed);
|
|
@@ -33,7 +33,7 @@ const normalizeAllowedRoute = (rawRoute, allowedRoutes) => {
|
|
|
33
33
|
if (direct)
|
|
34
34
|
return direct;
|
|
35
35
|
const lower = trimmed.toLowerCase();
|
|
36
|
-
//
|
|
36
|
+
// Allow case-insensitive matches, normalizing to the declared workflow route to avoid pointless failures.
|
|
37
37
|
const insensitive = allowedRoutes.find((route) => route.toLowerCase() === lower);
|
|
38
38
|
return insensitive ?? null;
|
|
39
39
|
};
|
|
@@ -96,7 +96,7 @@ const validateEnvelope = (envelope, ctx) => {
|
|
|
96
96
|
return { ok: false, code: "artifact_spec_mismatch" };
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
|
-
//
|
|
99
|
+
// Backward-compat with legacy/redundant fields: top-level route and decisions are no longer hard-failure conditions — simply ignore.
|
|
100
100
|
if (envelope.control !== undefined) {
|
|
101
101
|
if (!(0, guards_1.isRecord)(envelope.control))
|
|
102
102
|
return { ok: false, code: "hold_control_invalid" };
|
|
@@ -113,7 +113,7 @@ const validateEnvelope = (envelope, ctx) => {
|
|
|
113
113
|
return { ok: false, code: "route_content_invalid" };
|
|
114
114
|
}
|
|
115
115
|
if (!Array.isArray(primaryArtifact.content) && (0, guards_1.isRecord)(primaryArtifact.content)) {
|
|
116
|
-
//
|
|
116
|
+
// Accommodate single-object output by normalizing to an array so downstream routing logic stays consistent.
|
|
117
117
|
primaryArtifact.content = [primaryArtifact.content];
|
|
118
118
|
}
|
|
119
119
|
if (!Array.isArray(primaryArtifact.content) || primaryArtifact.content.length === 0) {
|
|
@@ -14,8 +14,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
//
|
|
18
|
-
//
|
|
17
|
+
// Directory barrel export: callers continue importing from structured-output sub-modules; internal files are split by responsibility.
|
|
18
|
+
// Re-export uniformly from here to avoid spreading external import paths to implementation details upon future re-splits.
|
|
19
19
|
__exportStar(require("./contract"), exports);
|
|
20
20
|
__exportStar(require("./parser"), exports);
|
|
21
21
|
__exportStar(require("./prompt"), exports);
|
|
@@ -31,8 +31,8 @@ exports.detectFenceLanguage = detectFenceLanguage;
|
|
|
31
31
|
const extractBalancedJsonObjectCandidates = (text) => {
|
|
32
32
|
const candidates = [];
|
|
33
33
|
const seen = new Set();
|
|
34
|
-
//
|
|
35
|
-
//
|
|
34
|
+
// Streaming output often has "explanatory text + JSON" concatenated in the same text segment.
|
|
35
|
+
// Use bracket-balanced scanning to extract embedded JSON objects, avoiding direct JSON.parse failure on the whole segment.
|
|
36
36
|
for (let start = text.indexOf("{"); start >= 0; start = text.indexOf("{", start + 1)) {
|
|
37
37
|
let depth = 0;
|
|
38
38
|
let inString = false;
|
|
@@ -130,7 +130,7 @@ const tryParseEnvelopeObject = (value) => {
|
|
|
130
130
|
continue;
|
|
131
131
|
const normalized = (0, contract_1.normalizeSchemaVersion)(rawArtifact.schemaVersion);
|
|
132
132
|
if (normalized !== null) {
|
|
133
|
-
//
|
|
133
|
+
// Loosely correct at parse stage; subsequent processing uniformly uses number semantic validation and persistence.
|
|
134
134
|
rawArtifact.schemaVersion = normalized;
|
|
135
135
|
}
|
|
136
136
|
}
|
|
@@ -147,7 +147,7 @@ const tryParseEnvelopeText = (text) => {
|
|
|
147
147
|
if (text.length > ENVELOPE_TEXT_SCAN_TAIL_CHARS) {
|
|
148
148
|
const headLen = text.length - ENVELOPE_TEXT_SCAN_TAIL_CHARS;
|
|
149
149
|
const headPart = text.slice(0, headLen);
|
|
150
|
-
//
|
|
150
|
+
// Backtrack to the nearest { to prevent the JSON start from being cut off by the window
|
|
151
151
|
const lastBrace = headPart.lastIndexOf("{");
|
|
152
152
|
scanText = lastBrace >= 0 ? text.slice(lastBrace) : text.slice(-ENVELOPE_TEXT_SCAN_TAIL_CHARS);
|
|
153
153
|
}
|
|
@@ -167,7 +167,7 @@ const tryParseEnvelopeText = (text) => {
|
|
|
167
167
|
return envelope;
|
|
168
168
|
}
|
|
169
169
|
catch {
|
|
170
|
-
//
|
|
170
|
+
// Continue trying subsequent candidates
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
return null;
|
|
@@ -106,16 +106,16 @@ const createNodeExecutionPrompt = (ctx) => {
|
|
|
106
106
|
return Array.from(grouped.values());
|
|
107
107
|
})();
|
|
108
108
|
const lines = [
|
|
109
|
-
"#
|
|
109
|
+
"# Pipeline Node Execution Instructions",
|
|
110
110
|
"",
|
|
111
|
-
"##
|
|
112
|
-
"-
|
|
113
|
-
"-
|
|
114
|
-
"- `status=success`
|
|
115
|
-
"- `artifacts[].content`
|
|
116
|
-
...(isRouteNode ? ["-
|
|
111
|
+
"## Output Requirements",
|
|
112
|
+
"- Return only a valid JSON object (ResultEnvelope).",
|
|
113
|
+
"- Do not output any extra explanations, prefixes, suffixes, or text outside of Markdown.",
|
|
114
|
+
"- When `status=success`, `artifacts` must contain at least 1 item.",
|
|
115
|
+
"- `artifacts[].content` accepts any JSON value (e.g., `\"text\"`, `{\"k\":\"v\"}`, `[{\"k\":\"v1\"},{\"k\":\"v2\"}]`).",
|
|
116
|
+
...(isRouteNode ? ["- For routing nodes, `artifacts[0].content` must be an array, and each object in the array must include a `route` field."] : []),
|
|
117
117
|
"",
|
|
118
|
-
"## ResultEnvelope
|
|
118
|
+
"## ResultEnvelope Fixed Fields",
|
|
119
119
|
`- version: \`2.0\``,
|
|
120
120
|
`- runId: \`${ctx.runId}\``,
|
|
121
121
|
`- nodeId: \`${ctx.nodeId}\``,
|
|
@@ -124,27 +124,27 @@ const createNodeExecutionPrompt = (ctx) => {
|
|
|
124
124
|
"- status: `success | failed`",
|
|
125
125
|
"- artifacts: `array`",
|
|
126
126
|
"",
|
|
127
|
-
"##
|
|
128
|
-
`-
|
|
129
|
-
`-
|
|
127
|
+
"## Node Context",
|
|
128
|
+
`- Current node: \`${ctx.nodeId}\` (${ctx.nodeTitle})`,
|
|
129
|
+
`- Dependencies: ${ctx.dependencies.length > 0 ? ctx.dependencies.map((id) => `\`${id}\``).join(", ") : "`none`"}`,
|
|
130
130
|
];
|
|
131
|
-
lines.push("", "##
|
|
132
|
-
//
|
|
131
|
+
lines.push("", "## Artifact Specification", `- type: \`${spec.type}\``, `- schemaVersion: \`${spec.schemaVersion}\``);
|
|
132
|
+
// Only inject routing instructions when the node explicitly enables routing, to avoid polluting the prompt when no routing is configured.
|
|
133
133
|
if (ctx.allowedRoutes.length > 0) {
|
|
134
|
-
lines.push("", "##
|
|
134
|
+
lines.push("", "## Routing Rules", `- Allowed routes are: ${ctx.allowedRoutes.map((route) => `\`${route}\``).join(", ")}`, `- \`${MAINLINE_ROUTE_VALUE}\` indicates continuing on the mainline; no routing target configuration needed.`, "- `artifacts[0].content` must be an array.", "- Each object in the array must include a `route` field.", "- Each object's `route` must match one of the allowed values above.", "- The system will automatically group and dispatch items by `content[*].route` to the corresponding branches.");
|
|
135
135
|
if (ctx.routeTargets.length > 0) {
|
|
136
|
-
lines.push("-
|
|
136
|
+
lines.push("- The configured route targets are:");
|
|
137
137
|
for (const target of ctx.routeTargets) {
|
|
138
138
|
lines.push(` - \`${target.route}\` -> \`${target.targetNodeId}\` (${target.targetNodeTitle}, agent:${target.targetAgentId}, lane:${target.lane})`);
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
|
-
//
|
|
142
|
+
// Only inject rejection instructions when the node explicitly enables rejection and has upstream dependencies, to avoid polluting the prompt when no rejection is configured.
|
|
143
143
|
if (ctx.allowReject && ctx.dependencies.length > 0) {
|
|
144
|
-
lines.push("", "##
|
|
144
|
+
lines.push("", "## Rejection Configuration", `- allowReject: \`${ctx.allowReject ? "true" : "false"}\``, `- maxRejectCount: \`${ctx.maxRejectCount}\``, "", "### Rejection Rules", "- Rejection is only allowed when upstream content does not meet your validation criteria.", "- When rejecting, you must return `status=failed` with `error.code=upstream_reject`.", "- To specify target upstream nodes, provide `error.targets=[\"nodeId\"]`.", "- When `targets` is not provided, the system defaults to rejecting the most recent direct upstream node.", "", "```json", "{\"code\":\"upstream_reject\",\"message\":\"rejection reason\"}", "```");
|
|
145
145
|
}
|
|
146
146
|
if (isRouteNode) {
|
|
147
|
-
lines.push("", "##
|
|
147
|
+
lines.push("", "## Routing Node JSON Example", "```json", JSON.stringify({
|
|
148
148
|
version: "2.0",
|
|
149
149
|
runId: "__RUN_ID__",
|
|
150
150
|
nodeId: "__NODE_ID__",
|
|
@@ -173,7 +173,7 @@ const createNodeExecutionPrompt = (ctx) => {
|
|
|
173
173
|
}, null, 2), "```");
|
|
174
174
|
}
|
|
175
175
|
else {
|
|
176
|
-
lines.push("", "## JSON
|
|
176
|
+
lines.push("", "## JSON Output Example", "```json", JSON.stringify({
|
|
177
177
|
version: "2.0",
|
|
178
178
|
runId: "__RUN_ID__",
|
|
179
179
|
nodeId: "__NODE_ID__",
|
|
@@ -202,22 +202,22 @@ const createNodeExecutionPrompt = (ctx) => {
|
|
|
202
202
|
if (ctx.externalPipelineArtifact) {
|
|
203
203
|
const artifact = ctx.externalPipelineArtifact;
|
|
204
204
|
const fence = (0, parser_1.detectFenceLanguage)(artifact.content);
|
|
205
|
-
lines.push("##
|
|
205
|
+
lines.push("## External Pipeline Upstream Artifacts", "", `Source: final output of pipeline ${artifact.sourcePipelineId}`, "", "Content:", `\`\`\`${fence}`, artifact.content, "```");
|
|
206
206
|
}
|
|
207
207
|
if (groupedDependencies.length > 0) {
|
|
208
|
-
lines.push("##
|
|
208
|
+
lines.push("## Upstream Output Structure:");
|
|
209
209
|
for (const dep of groupedDependencies) {
|
|
210
210
|
const merged = dep.contents.join("\n");
|
|
211
211
|
const fence = (0, parser_1.detectFenceLanguage)(merged);
|
|
212
|
-
lines.push(`###
|
|
212
|
+
lines.push(`### Node \`${dep.sourceNodeId}\` (${dep.sourceNodeTitle}) - agent \`${dep.sourceAgentId}\``, `\`\`\`${fence}`, merged, "```");
|
|
213
213
|
}
|
|
214
214
|
lines.push("");
|
|
215
215
|
}
|
|
216
|
-
//
|
|
217
|
-
//
|
|
218
|
-
lines.push("##
|
|
216
|
+
// The node objective is the core constraint of the execution prompt and must not be overwritten by rejection feedback or routing instructions.
|
|
217
|
+
// Always keep the "Node Objective" section here, then append feedback separately, to avoid routing-node prompts missing the main task objective.
|
|
218
|
+
lines.push("## Node Objective", ctx.instruction || "Complete the task according to the node's responsibilities");
|
|
219
219
|
if (ctx.rejectFeedbacks.length > 0) {
|
|
220
|
-
lines.push("", "##
|
|
220
|
+
lines.push("", "## Downstream Rejection Feedback (please prioritize fixes)");
|
|
221
221
|
for (const item of ctx.rejectFeedbacks) {
|
|
222
222
|
lines.push(`- ${item}`);
|
|
223
223
|
}
|
|
@@ -226,28 +226,28 @@ const createNodeExecutionPrompt = (ctx) => {
|
|
|
226
226
|
};
|
|
227
227
|
exports.createNodeExecutionPrompt = createNodeExecutionPrompt;
|
|
228
228
|
const createNodeCorrectionPrompt = (ctx, lastViolation) => [
|
|
229
|
-
|
|
230
|
-
"
|
|
231
|
-
|
|
232
|
-
|
|
229
|
+
`Structural validation failed, error code: ${lastViolation}`,
|
|
230
|
+
"Resend a complete ResultEnvelope strictly based on the fixed fields of this request. Do not reuse top-level fields from previous requests.",
|
|
231
|
+
`Fixed fields must be: version="2.0", runId="${ctx.runId}", nodeId="${ctx.nodeId}", requestId="${ctx.requestId}", sessionId="${ctx.sessionId}"`,
|
|
232
|
+
`Artifact specification must be: type="${ctx.outputSpec.type}", schemaVersion=${ctx.outputSpec.schemaVersion}`,
|
|
233
233
|
ctx.allowedRoutes && ctx.allowedRoutes.length > 0
|
|
234
|
-
?
|
|
234
|
+
? `This is a routing node. The only allowed routes are: ${ctx.allowedRoutes.map((route) => `"${route}"`).join(", ")}. Include the route field in each object within artifacts[0].content.`
|
|
235
235
|
: ctx.externalPipelineArtifact
|
|
236
|
-
?
|
|
237
|
-
: "
|
|
238
|
-
"
|
|
236
|
+
? `This request contains an external upstream artifact from pipeline ${ctx.externalPipelineArtifact.sourcePipelineId}. Continue corrections based on this upstream artifact.`
|
|
237
|
+
: "Output the result in the current structure.",
|
|
238
|
+
"First save the complete ResultEnvelope as result.json in the current working directory, then run the following validation command and confirm JSON valid before continuing:",
|
|
239
239
|
"```bash",
|
|
240
240
|
"cat result.json | python3 -m json.tool > /dev/null && echo \"JSON valid\" || echo \"JSON invalid\"",
|
|
241
241
|
"```",
|
|
242
|
-
"
|
|
243
|
-
"
|
|
244
|
-
"
|
|
242
|
+
"If the validation result is not JSON valid, continue fixing result.json until it passes.",
|
|
243
|
+
"You must output the complete JSON object, not just the artifacts fragment.",
|
|
244
|
+
"Output only the corrected valid JSON ResultEnvelope. Do not output any explanations.",
|
|
245
245
|
].join("\n");
|
|
246
246
|
exports.createNodeCorrectionPrompt = createNodeCorrectionPrompt;
|
|
247
247
|
const buildExternalPipelineArtifactInput = async (output) => {
|
|
248
248
|
try {
|
|
249
249
|
const { absolutePath } = output.artifactRef;
|
|
250
|
-
//
|
|
250
|
+
// Normalize path to resolve relative segments (e.g. '..', '.')
|
|
251
251
|
const normalizedPath = (0, node_path_1.resolve)(absolutePath);
|
|
252
252
|
if (!normalizedPath)
|
|
253
253
|
return null;
|
|
@@ -54,8 +54,8 @@ const evaluateObservedEnvelopeWindow = (observed, ctx, options) => {
|
|
|
54
54
|
const forRequestId = observed.filter((entry) => entry.envelope.requestId === ctx.requestId);
|
|
55
55
|
if (forRequestId.length > 0) {
|
|
56
56
|
if (!confirmFinal) {
|
|
57
|
-
// agent
|
|
58
|
-
//
|
|
57
|
+
// Before the agent session ends, only record "candidate seen" — cannot prematurely declare success/failure.
|
|
58
|
+
// Otherwise intermediate debug JSON or half-finished envelopes could be consumed too early.
|
|
59
59
|
return { seenCandidate: true };
|
|
60
60
|
}
|
|
61
61
|
let latestFailure = null;
|
|
@@ -80,12 +80,12 @@ const evaluateObservedEnvelopeWindow = (observed, ctx, options) => {
|
|
|
80
80
|
};
|
|
81
81
|
exports.evaluateObservedEnvelopeWindow = evaluateObservedEnvelopeWindow;
|
|
82
82
|
/**
|
|
83
|
-
*
|
|
83
|
+
* Wait for a structured receipt.
|
|
84
84
|
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
85
|
+
* When an AbortSignal is passed and triggered, exit the local polling loop early.
|
|
86
|
+
* Remote agent stopping is handled by the upstream executionService via the "/stop" command.
|
|
87
87
|
*
|
|
88
|
-
* @param signal
|
|
88
|
+
* @param signal Optional abort signal, used to exit polling early during pipeline stop/retry.
|
|
89
89
|
*/
|
|
90
90
|
const waitForStructuredEnvelope = async (emitter, ctx, initialViolation, hasSessionCompleted, signal) => {
|
|
91
91
|
const deadline = Date.now() + STRUCTURED_RESULT_TIMEOUT_MS;
|
|
@@ -69,30 +69,30 @@ exports.readTemplateNodesFromRaw = readTemplateNodesFromRaw;
|
|
|
69
69
|
// ====== Migration ======
|
|
70
70
|
const migrateWorkflowDefinitionV2RawToV3 = (value) => {
|
|
71
71
|
if (!(0, guards_1.isRecord)(value) || value.version !== "2.0") {
|
|
72
|
-
return { ok: false, error: "invalid_workflow_definition", detail: "
|
|
72
|
+
return { ok: false, error: "invalid_workflow_definition", detail: "Migration is only supported from workflow v2.0" };
|
|
73
73
|
}
|
|
74
74
|
if (!Array.isArray(value.nodes) || !Array.isArray(value.edges) || !Array.isArray(value.groups)) {
|
|
75
|
-
return { ok: false, error: "invalid_workflow_definition", detail: "workflow.nodes/edges/groups
|
|
75
|
+
return { ok: false, error: "invalid_workflow_definition", detail: "workflow.nodes/edges/groups must be arrays" };
|
|
76
76
|
}
|
|
77
77
|
const nodes = [];
|
|
78
78
|
for (const item of value.nodes) {
|
|
79
79
|
const normalized = (0, normalize_1.normalizeWorkflowNode)(item);
|
|
80
80
|
if (!normalized)
|
|
81
|
-
return { ok: false, error: "invalid_workflow_definition", detail: "workflow.nodes
|
|
81
|
+
return { ok: false, error: "invalid_workflow_definition", detail: "workflow.nodes contains an invalid node structure" };
|
|
82
82
|
nodes.push(normalized);
|
|
83
83
|
}
|
|
84
84
|
const edges = [];
|
|
85
85
|
for (const item of value.edges) {
|
|
86
86
|
const normalized = (0, normalize_1.normalizeWorkflowEdge)(item);
|
|
87
87
|
if (!normalized)
|
|
88
|
-
return { ok: false, error: "invalid_workflow_definition", detail: "workflow.edges
|
|
88
|
+
return { ok: false, error: "invalid_workflow_definition", detail: "workflow.edges contains an invalid edge structure" };
|
|
89
89
|
edges.push(normalized);
|
|
90
90
|
}
|
|
91
91
|
const groups = [];
|
|
92
92
|
for (const item of value.groups) {
|
|
93
93
|
const normalized = (0, normalize_1.normalizeWorkflowGroup)(item);
|
|
94
94
|
if (!normalized)
|
|
95
|
-
return { ok: false, error: "invalid_workflow_definition", detail: "workflow.groups
|
|
95
|
+
return { ok: false, error: "invalid_workflow_definition", detail: "workflow.groups contains an invalid parallel group structure" };
|
|
96
96
|
groups.push(normalized);
|
|
97
97
|
}
|
|
98
98
|
const workflow = {
|
|
@@ -7,8 +7,8 @@ const LOG_FILE_NAME = "timeline.log";
|
|
|
7
7
|
const stringifyTimelineEntry = (entry) => {
|
|
8
8
|
const seen = new WeakSet();
|
|
9
9
|
return JSON.stringify(entry, (_key, value) => {
|
|
10
|
-
//
|
|
11
|
-
//
|
|
10
|
+
// Allow keeping the full detail content, but cyclic-referencing objects themselves can't be directly JSON-serialized;
|
|
11
|
+
// this is purely a safety net to prevent log persistence from being interrupted by an anomalous object in the main flow.
|
|
12
12
|
if (typeof value !== "object" || value === null)
|
|
13
13
|
return value;
|
|
14
14
|
if (seen.has(value))
|
|
@@ -30,7 +30,7 @@ const createTimelineLogStore = (options) => {
|
|
|
30
30
|
text: item.text,
|
|
31
31
|
...(item.detail === undefined ? {} : { detail: item.detail }),
|
|
32
32
|
})}\n`;
|
|
33
|
-
//
|
|
33
|
+
// Per-line size safety net to prevent oversized details from blowing up the log file
|
|
34
34
|
const MAX_LOG_LINE_BYTES = 512 * 1024;
|
|
35
35
|
if (Buffer.byteLength(line, "utf8") > MAX_LOG_LINE_BYTES) {
|
|
36
36
|
const truncated = line.slice(0, MAX_LOG_LINE_BYTES);
|
|
@@ -40,14 +40,14 @@ const createTimelineLogStore = (options) => {
|
|
|
40
40
|
}
|
|
41
41
|
writeChain = writeChain
|
|
42
42
|
.catch(() => {
|
|
43
|
-
//
|
|
43
|
+
// Continue subsequent writes after a previous flush failure to avoid permanently deadlocking the entire queue.
|
|
44
44
|
})
|
|
45
45
|
.then(async () => {
|
|
46
46
|
await (0, promises_1.mkdir)((0, node_path_1.dirname)(logFile), { recursive: true });
|
|
47
47
|
await (0, promises_1.appendFile)(logFile, line, "utf8");
|
|
48
48
|
});
|
|
49
49
|
return writeChain.catch(() => {
|
|
50
|
-
//
|
|
50
|
+
// Log persistence failure must not affect pipeline execution; swallow the exception here and let the caller record it as needed.
|
|
51
51
|
});
|
|
52
52
|
};
|
|
53
53
|
return {
|
|
@@ -77,15 +77,15 @@ const createToolActivityLogger = (deps) => {
|
|
|
77
77
|
if (!markSeen(dedupeKey))
|
|
78
78
|
return;
|
|
79
79
|
if (phase === "start") {
|
|
80
|
-
deps.pushTimeline(`Agent ${agentId}
|
|
80
|
+
deps.pushTimeline(`Agent ${agentId} tool started: ${toolName} (run:${runId})`);
|
|
81
81
|
return;
|
|
82
82
|
}
|
|
83
83
|
if (phase === "result" || phase === "end") {
|
|
84
84
|
const isError = data.isError === true;
|
|
85
|
-
deps.pushTimeline(`Agent ${agentId}
|
|
85
|
+
deps.pushTimeline(`Agent ${agentId} tool finished: ${toolName} (run:${runId}${isError ? ", error" : ""})`, isError ? "warn" : "info");
|
|
86
86
|
return;
|
|
87
87
|
}
|
|
88
|
-
deps.pushTimeline(`Agent ${agentId}
|
|
88
|
+
deps.pushTimeline(`Agent ${agentId} tool event: ${toolName}/${phase || "unknown"} (run:${runId})`);
|
|
89
89
|
};
|
|
90
90
|
return {
|
|
91
91
|
handleFrame,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.buildOutputId = void 0;
|
|
4
4
|
const node_crypto_1 = require("node:crypto");
|
|
5
|
-
/**
|
|
5
|
+
/** Compute a short hash from a deduplication key; stable dedup with manageable length. */
|
|
6
6
|
const buildOutputId = (pipelineId, runId, batchRunId, itemKey, outputNodeId, artifactId, hash) => {
|
|
7
7
|
const key = `${pipelineId}|${runId}|${batchRunId ?? ""}|${itemKey ?? ""}|${outputNodeId}|${artifactId}|${hash}`;
|
|
8
8
|
const digest = (0, node_crypto_1.createHash)("sha256").update(key).digest("hex").slice(0, 16);
|