taskmeld 0.1.1 → 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/LICENSE +1 -1
- package/README.md +176 -172
- package/README.zh-CN.md +176 -172
- 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 +16 -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
|
@@ -12,8 +12,8 @@ const DEFAULT_RETENTION_DAYS = {
|
|
|
12
12
|
};
|
|
13
13
|
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* Generate a cleanup plan without deleting any files.
|
|
16
|
+
* Defaults to only cleaning "success" status; other statuses can be specified via options.
|
|
17
17
|
*/
|
|
18
18
|
const planCleanup = async (definition, options = {}) => {
|
|
19
19
|
const statuses = options.statuses?.length ? options.statuses : ["success"];
|
|
@@ -22,7 +22,7 @@ const planCleanup = async (definition, options = {}) => {
|
|
|
22
22
|
const kindFilter = options.kinds?.length ? new Set(options.kinds) : null;
|
|
23
23
|
const olderThanDays = options.olderThanDays ?? Math.max(...safeStatuses.map((s) => DEFAULT_RETENTION_DAYS[s] ?? 90));
|
|
24
24
|
const cutoffMs = Date.now() - olderThanDays * ONE_DAY_MS;
|
|
25
|
-
//
|
|
25
|
+
// Prefer index for file list; fall back to scan when index is missing
|
|
26
26
|
let items = await (0, artifact_index_1.readIndexRecords)(definition.artifactDir);
|
|
27
27
|
if (items.length === 0) {
|
|
28
28
|
const scanned = await (0, storage_service_1.scanStoredArtifacts)([definition]);
|
|
@@ -85,7 +85,7 @@ const planCleanup = async (definition, options = {}) => {
|
|
|
85
85
|
};
|
|
86
86
|
exports.planCleanup = planCleanup;
|
|
87
87
|
/**
|
|
88
|
-
*
|
|
88
|
+
* Execute cleanup: delete files + clean empty directories + clean temp files + rebuild index.
|
|
89
89
|
*/
|
|
90
90
|
const executeCleanup = async (definition, plan) => {
|
|
91
91
|
const warnings = [];
|
|
@@ -94,10 +94,10 @@ const executeCleanup = async (definition, plan) => {
|
|
|
94
94
|
for (const file of plan.files) {
|
|
95
95
|
const rootAbs = (0, node_path_1.resolve)(definition.artifactDir);
|
|
96
96
|
const absPath = (0, node_path_1.resolve)(rootAbs, file.relativePath);
|
|
97
|
-
//
|
|
97
|
+
// Path traversal protection: target must be within the current pipeline's artifactDir
|
|
98
98
|
if (absPath !== rootAbs && !absPath.startsWith(`${rootAbs}${node_path_1.sep}`)) {
|
|
99
99
|
failed += 1;
|
|
100
|
-
warnings.push(
|
|
100
|
+
warnings.push(`Refused to delete out-of-bounds file (${definition.id}:${file.relativePath})`);
|
|
101
101
|
continue;
|
|
102
102
|
}
|
|
103
103
|
try {
|
|
@@ -106,27 +106,27 @@ const executeCleanup = async (definition, plan) => {
|
|
|
106
106
|
}
|
|
107
107
|
catch (error) {
|
|
108
108
|
failed += 1;
|
|
109
|
-
warnings.push(
|
|
109
|
+
warnings.push(`Delete failed (${definition.id}:${file.relativePath}): ${error instanceof Error ? error.message : String(error)}`);
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
|
-
//
|
|
112
|
+
// Clean empty directories
|
|
113
113
|
await (0, exports.cleanupEmptyDirs)(definition.artifactDir);
|
|
114
|
-
//
|
|
114
|
+
// Clean temp files
|
|
115
115
|
const tmpResult = await (0, exports.cleanupTempFiles)(definition.artifactDir);
|
|
116
116
|
warnings.push(...tmpResult.warnings);
|
|
117
|
-
//
|
|
117
|
+
// Rebuild index to remove deleted file entries
|
|
118
118
|
try {
|
|
119
119
|
const { rebuildArtifactIndex } = await import("./artifact-index.js");
|
|
120
120
|
await rebuildArtifactIndex(definition, (d) => (0, storage_service_1.scanStoredArtifacts)([d]));
|
|
121
121
|
}
|
|
122
122
|
catch {
|
|
123
|
-
warnings.push(
|
|
123
|
+
warnings.push(`Auto-rebuild index after delete failed (${definition.id}), please run rebuild-index manually`);
|
|
124
124
|
}
|
|
125
125
|
return { deleted, failed, warnings };
|
|
126
126
|
};
|
|
127
127
|
exports.executeCleanup = executeCleanup;
|
|
128
128
|
/**
|
|
129
|
-
*
|
|
129
|
+
* Recursively clean empty directories. Preserves the artifactDir root directory itself.
|
|
130
130
|
*/
|
|
131
131
|
const cleanupEmptyDirs = async (artifactDir) => {
|
|
132
132
|
const warnings = [];
|
|
@@ -139,7 +139,7 @@ const cleanupEmptyDirs = async (artifactDir) => {
|
|
|
139
139
|
await walkAndRemove(`${dirPath}${node_path_1.sep}${entry.name}`);
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
|
-
//
|
|
142
|
+
// Don't delete the artifacts root directory itself
|
|
143
143
|
const isRoot = dirPath === artifactDir || dirPath.endsWith(`${node_path_1.sep}artifacts`) && dirPath.replace(/\\/g, "/").endsWith("/artifacts");
|
|
144
144
|
if (isRoot)
|
|
145
145
|
return;
|
|
@@ -150,7 +150,7 @@ const cleanupEmptyDirs = async (artifactDir) => {
|
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
catch {
|
|
153
|
-
//
|
|
153
|
+
// Directory doesn't exist or cannot be accessed
|
|
154
154
|
}
|
|
155
155
|
};
|
|
156
156
|
await walkAndRemove(artifactDir);
|
|
@@ -158,7 +158,7 @@ const cleanupEmptyDirs = async (artifactDir) => {
|
|
|
158
158
|
};
|
|
159
159
|
exports.cleanupEmptyDirs = cleanupEmptyDirs;
|
|
160
160
|
/**
|
|
161
|
-
*
|
|
161
|
+
* Clean up .tmp-*.json temp files left behind by persistArtifactFile (atomic write failure residue).
|
|
162
162
|
*/
|
|
163
163
|
const cleanupTempFiles = async (artifactDir) => {
|
|
164
164
|
const warnings = [];
|
|
@@ -177,13 +177,13 @@ const cleanupTempFiles = async (artifactDir) => {
|
|
|
177
177
|
cleaned += 1;
|
|
178
178
|
}
|
|
179
179
|
catch (error) {
|
|
180
|
-
warnings.push(
|
|
180
|
+
warnings.push(`Failed to clean temp files: ${error instanceof Error ? error.message : String(error)}`);
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
catch {
|
|
186
|
-
//
|
|
186
|
+
// Directory doesn't exist
|
|
187
187
|
}
|
|
188
188
|
};
|
|
189
189
|
await walkAndClean(artifactDir);
|
|
@@ -9,7 +9,7 @@ const INDEX_FILE_NAME = "index.jsonl";
|
|
|
9
9
|
const getIndexPath = (artifactDir) => (0, node_path_1.resolve)(artifactDir, INDEX_FILE_NAME);
|
|
10
10
|
exports.getIndexPath = getIndexPath;
|
|
11
11
|
const encodeCursorV2 = (cursor) => `v2:${Buffer.from(JSON.stringify(cursor), "utf8").toString("base64url")}`;
|
|
12
|
-
/**
|
|
12
|
+
/** Compatible decoding for v2 and legacy cursors. Returns null for invalid input. */
|
|
13
13
|
const decodeAnyCursor = (cursor) => {
|
|
14
14
|
if (cursor.startsWith("v2:")) {
|
|
15
15
|
try {
|
|
@@ -23,7 +23,7 @@ const decodeAnyCursor = (cursor) => {
|
|
|
23
23
|
return null;
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
//
|
|
26
|
+
// Legacy v1 cursor format: base64url(updatedAt|artifactId)
|
|
27
27
|
try {
|
|
28
28
|
const raw = Buffer.from(cursor, "base64url").toString("utf8");
|
|
29
29
|
const idx = raw.lastIndexOf("|");
|
|
@@ -44,7 +44,7 @@ const getIndexUpdatedAt = async (artifactDir) => {
|
|
|
44
44
|
return "";
|
|
45
45
|
}
|
|
46
46
|
};
|
|
47
|
-
/**
|
|
47
|
+
/** Append one index record to index.jsonl. Does not throw on failure; returns failure info for caller observation. */
|
|
48
48
|
const appendIndexRecord = async (artifactDir, record) => {
|
|
49
49
|
const indexPath = (0, exports.getIndexPath)(artifactDir);
|
|
50
50
|
try {
|
|
@@ -65,7 +65,7 @@ const appendIndexRecord = async (artifactDir, record) => {
|
|
|
65
65
|
}
|
|
66
66
|
};
|
|
67
67
|
exports.appendIndexRecord = appendIndexRecord;
|
|
68
|
-
/**
|
|
68
|
+
/** Read all lines from the index file, skipping corrupt lines. Uses streaming reads to avoid loading a large index entirely into memory. */
|
|
69
69
|
const readIndexRecords = async (artifactDir) => {
|
|
70
70
|
const indexPath = (0, exports.getIndexPath)(artifactDir);
|
|
71
71
|
const records = [];
|
|
@@ -81,7 +81,7 @@ const readIndexRecords = async (artifactDir) => {
|
|
|
81
81
|
records.push(JSON.parse(line));
|
|
82
82
|
}
|
|
83
83
|
catch {
|
|
84
|
-
//
|
|
84
|
+
// Skip corrupt lines
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
}
|
|
@@ -101,7 +101,7 @@ const dedupeLatestByArtifactId = (records) => {
|
|
|
101
101
|
}
|
|
102
102
|
return [...latest.values()];
|
|
103
103
|
};
|
|
104
|
-
/**
|
|
104
|
+
/** Read from index, filter, sort, paginate. Builds on top of readIndexRecords to provide query capabilities. */
|
|
105
105
|
const listIndexRecords = async (artifactDir, filter) => {
|
|
106
106
|
const records = dedupeLatestByArtifactId(await (0, exports.readIndexRecords)(artifactDir));
|
|
107
107
|
const limitRaw = filter.limit ?? 100;
|
|
@@ -133,7 +133,7 @@ const listIndexRecords = async (artifactDir, filter) => {
|
|
|
133
133
|
continue;
|
|
134
134
|
matched.push(record);
|
|
135
135
|
}
|
|
136
|
-
//
|
|
136
|
+
// Sort by updatedAt DESC, artifactId ASC
|
|
137
137
|
matched.sort((a, b) => {
|
|
138
138
|
const dateDiff = Date.parse(b.updatedAt) - Date.parse(a.updatedAt);
|
|
139
139
|
if (dateDiff !== 0)
|
|
@@ -141,13 +141,13 @@ const listIndexRecords = async (artifactDir, filter) => {
|
|
|
141
141
|
return a.artifactId.localeCompare(b.artifactId);
|
|
142
142
|
});
|
|
143
143
|
const total = matched.length;
|
|
144
|
-
// cursor
|
|
144
|
+
// cursor pagination: v2 cursor includes indexUpdatedAt; after index rebuild, old cursors automatically restart from the beginning
|
|
145
145
|
let startIndex = 0;
|
|
146
146
|
const currentIndexUpdatedAt = await getIndexUpdatedAt(artifactDir);
|
|
147
147
|
if (filter.cursor) {
|
|
148
148
|
const decoded = decodeAnyCursor(filter.cursor);
|
|
149
149
|
if (decoded) {
|
|
150
|
-
//
|
|
150
|
+
// Index generation changed → cursor expired, restart from the beginning
|
|
151
151
|
if (decoded.indexUpdatedAt && decoded.indexUpdatedAt !== currentIndexUpdatedAt) {
|
|
152
152
|
startIndex = 0;
|
|
153
153
|
}
|
|
@@ -165,7 +165,7 @@ const listIndexRecords = async (artifactDir, filter) => {
|
|
|
165
165
|
return { items: page, nextCursor, total };
|
|
166
166
|
};
|
|
167
167
|
exports.listIndexRecords = listIndexRecords;
|
|
168
|
-
/**
|
|
168
|
+
/** Convert from StoredArtifactIndexRecord to StoredArtifactItem. */
|
|
169
169
|
const toStoredArtifactItem = (record, pipelineId, pipelineTitle) => ({
|
|
170
170
|
pipelineId,
|
|
171
171
|
pipelineTitle,
|
|
@@ -181,7 +181,7 @@ const toStoredArtifactItem = (record, pipelineId, pipelineTitle) => ({
|
|
|
181
181
|
});
|
|
182
182
|
exports.toStoredArtifactItem = toStoredArtifactItem;
|
|
183
183
|
/**
|
|
184
|
-
*
|
|
184
|
+
* Scan a single pipeline's artifact directory and rebuild its index.jsonl.
|
|
185
185
|
*/
|
|
186
186
|
const rebuildArtifactIndex = async (definition, scan) => {
|
|
187
187
|
const warnings = [];
|
|
@@ -207,12 +207,12 @@ const rebuildArtifactIndex = async (definition, scan) => {
|
|
|
207
207
|
await (0, promises_1.rename)(tmpPath, indexPath);
|
|
208
208
|
}
|
|
209
209
|
catch (error) {
|
|
210
|
-
warnings.push(
|
|
210
|
+
warnings.push(`Failed to rebuild index (pipeline ${definition.id}): ${error instanceof Error ? error.message : String(error)}`);
|
|
211
211
|
}
|
|
212
212
|
return { indexed, skipped, warnings };
|
|
213
213
|
};
|
|
214
214
|
exports.rebuildArtifactIndex = rebuildArtifactIndex;
|
|
215
|
-
/**
|
|
215
|
+
/** Enrich a StoredArtifactItem with its file content to produce a complete IndexRecord. */
|
|
216
216
|
const enrichItemToIndexRecord = async (item, definition) => {
|
|
217
217
|
const filePath = (0, node_path_1.resolve)(definition.artifactDir, item.relativePath);
|
|
218
218
|
let parsed = null;
|
|
@@ -221,7 +221,7 @@ const enrichItemToIndexRecord = async (item, definition) => {
|
|
|
221
221
|
parsed = JSON.parse(raw);
|
|
222
222
|
}
|
|
223
223
|
catch {
|
|
224
|
-
//
|
|
224
|
+
// When unreadable, use path-based heuristics
|
|
225
225
|
}
|
|
226
226
|
const artifactObj = parsed && typeof parsed.artifact === "object" && parsed.artifact !== null
|
|
227
227
|
? parsed.artifact
|
|
@@ -5,8 +5,8 @@ const promises_1 = require("node:fs/promises");
|
|
|
5
5
|
const node_path_1 = require("node:path");
|
|
6
6
|
const artifact_index_1 = require("./artifact-index");
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Incrementally rebuild artifact index — scan the disk directory, only append records for files missing from the index.
|
|
9
|
+
* Files already present in the index (matched by relativePath) are not re-added.
|
|
10
10
|
*/
|
|
11
11
|
const rebuildArtifactIndexIncremental = async (rootDir, pipelineId) => {
|
|
12
12
|
const warnings = [];
|
|
@@ -36,7 +36,7 @@ const rebuildArtifactIndexIncremental = async (rootDir, pipelineId) => {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
catch {
|
|
39
|
-
warnings.push(
|
|
39
|
+
warnings.push(`Failed to scan artifact root directory: ${rootDir}`);
|
|
40
40
|
}
|
|
41
41
|
for (const dir of dirs) {
|
|
42
42
|
const existingRecords = await (0, artifact_index_1.readIndexRecords)(dir.artifactDir);
|
|
@@ -39,7 +39,7 @@ const parseNodeIdFromFileName = (fileName, runId, relativePath) => {
|
|
|
39
39
|
return null;
|
|
40
40
|
const normalizedPath = toUnixPath(relativePath).toLowerCase();
|
|
41
41
|
if (normalizedPath.includes("/envelopes/")) {
|
|
42
|
-
// envelope
|
|
42
|
+
// envelope filename format: <runId>-<nodeId>-node-<nodeId>-<uuid>-envelope.json
|
|
43
43
|
const marker = "-node-";
|
|
44
44
|
const markerIndex = tail.indexOf(marker);
|
|
45
45
|
if (markerIndex > 0) {
|
|
@@ -48,13 +48,13 @@ const parseNodeIdFromFileName = (fileName, runId, relativePath) => {
|
|
|
48
48
|
}
|
|
49
49
|
return null;
|
|
50
50
|
}
|
|
51
|
-
// group/
|
|
51
|
+
// group/adapter aggregate file format is unstable (includes itemKey); skip node filtering here to avoid false positives.
|
|
52
52
|
if (tail.endsWith("-adapter-output") || tail.endsWith("-group-output"))
|
|
53
53
|
return null;
|
|
54
54
|
const tokens = tail.split("-").filter(Boolean);
|
|
55
55
|
if (tokens.length < 3)
|
|
56
56
|
return null;
|
|
57
|
-
//
|
|
57
|
+
// Structured node artifact naming format: <runId>-<nodeId>-<artifactIndex>-<safeType>.json
|
|
58
58
|
let artifactIndexPos = -1;
|
|
59
59
|
for (let i = tokens.length - 1; i >= 0; i -= 1) {
|
|
60
60
|
if (NUMERIC_TOKEN_RE.test(tokens[i])) {
|
|
@@ -102,7 +102,7 @@ const parseArtifactPathMeta = (artifactRootAbs, filePathAbs, updatedAtIso) => {
|
|
|
102
102
|
const dateBucket = DATE_BUCKET_RE.test(dateRaw) ? dateRaw : formatDateBucketFromIso(updatedAtIso);
|
|
103
103
|
let runId = null;
|
|
104
104
|
if (segments[2]?.startsWith("batch-")) {
|
|
105
|
-
//
|
|
105
|
+
// Batch run artifact path: status/date/batchRunId/runId/...
|
|
106
106
|
runId = segments[3]?.startsWith("run-") ? segments[3] : null;
|
|
107
107
|
}
|
|
108
108
|
else {
|
|
@@ -116,7 +116,7 @@ const shouldIncludeArtifactFile = (relativePath) => {
|
|
|
116
116
|
return false;
|
|
117
117
|
return true;
|
|
118
118
|
};
|
|
119
|
-
/**
|
|
119
|
+
/** Filesystem scan (fallback path / for index rebuild). */
|
|
120
120
|
const scanStoredArtifacts = async (definitions, options) => {
|
|
121
121
|
const pipelineIdFilter = options?.pipelineIds?.length ? new Set(options.pipelineIds) : null;
|
|
122
122
|
const nodeIdFilter = options?.nodeIds?.length ? new Set(options.nodeIds) : null;
|
|
@@ -150,11 +150,11 @@ const scanStoredArtifacts = async (definitions, options) => {
|
|
|
150
150
|
continue;
|
|
151
151
|
if (runIdFilter && effectiveRunId !== runIdFilter)
|
|
152
152
|
continue;
|
|
153
|
-
// batchRunId
|
|
153
|
+
// batchRunId scan filter: path contains batch-xxx segment
|
|
154
154
|
if (batchRunIdFilter && !meta.relativePath.includes(`/${batchRunIdFilter}/`))
|
|
155
155
|
continue;
|
|
156
156
|
const nodeId = parseNodeIdFromFileName(fileName, effectiveRunId, meta.relativePath);
|
|
157
|
-
// kind
|
|
157
|
+
// kind scan filter: determined by filename suffix and path
|
|
158
158
|
if (kindFilter) {
|
|
159
159
|
const scanKind = meta.relativePath.includes("/envelopes/") ? "envelope"
|
|
160
160
|
: fileName.includes("-adapter-output") ? "adapter"
|
|
@@ -203,11 +203,11 @@ const decodeListCursor = (cursor) => {
|
|
|
203
203
|
return null;
|
|
204
204
|
}
|
|
205
205
|
};
|
|
206
|
-
/**
|
|
206
|
+
/** Prefer reading from index; fall back to filesystem scan when index is missing. Supports cursor pagination. */
|
|
207
207
|
const listStoredArtifacts = async (definitions, options) => {
|
|
208
208
|
const pipelineIdFilter = options?.pipelineIds?.length ? new Set(options.pipelineIds) : null;
|
|
209
209
|
const limit = Number.isFinite(options?.limit) ? Math.max(1, Math.min(5000, Math.trunc(options?.limit))) : 100;
|
|
210
|
-
//
|
|
210
|
+
// Try reading from index (without cursor; pagination is handled uniformly by the outer layer)
|
|
211
211
|
const indexItems = [];
|
|
212
212
|
let indexAvailable = false;
|
|
213
213
|
for (const definition of definitions) {
|
|
@@ -222,7 +222,7 @@ const listStoredArtifacts = async (definitions, options) => {
|
|
|
222
222
|
dateTo: options?.dateTo,
|
|
223
223
|
batchRunId: options?.batchRunId,
|
|
224
224
|
runId: options?.runId,
|
|
225
|
-
limit: 0, // 0 =
|
|
225
|
+
limit: 0, // 0 = unlimited; read all then sort and paginate uniformly
|
|
226
226
|
};
|
|
227
227
|
const result = await (0, artifact_index_1.listIndexRecords)(definition.artifactDir, filter);
|
|
228
228
|
if (result.total > 0 || result.items.length > 0) {
|
|
@@ -232,7 +232,7 @@ const listStoredArtifacts = async (definitions, options) => {
|
|
|
232
232
|
}
|
|
233
233
|
}
|
|
234
234
|
}
|
|
235
|
-
//
|
|
235
|
+
// Unified sort and cursor pagination (index and scan use the same encoding)
|
|
236
236
|
const sortItems = (items) => items.sort((a, b) => Date.parse(b.updatedAt) - Date.parse(a.updatedAt));
|
|
237
237
|
const paginateWithCursor = (items, source) => {
|
|
238
238
|
let startIndex = 0;
|
|
@@ -255,7 +255,7 @@ const listStoredArtifacts = async (definitions, options) => {
|
|
|
255
255
|
if (indexAvailable) {
|
|
256
256
|
return paginateWithCursor(sortItems(indexItems), "index");
|
|
257
257
|
}
|
|
258
|
-
//
|
|
258
|
+
// Fallback: filesystem scan
|
|
259
259
|
const scanItems = await (0, exports.scanStoredArtifacts)(definitions, {
|
|
260
260
|
pipelineIds: options?.pipelineIds,
|
|
261
261
|
nodeIds: options?.nodeIds,
|
|
@@ -272,7 +272,7 @@ exports.listStoredArtifacts = listStoredArtifacts;
|
|
|
272
272
|
const resolveArtifactFilePath = (definition, relativePath) => {
|
|
273
273
|
const rootAbs = (0, node_path_1.resolve)(definition.artifactDir);
|
|
274
274
|
const targetAbs = (0, node_path_1.resolve)(rootAbs, relativePath);
|
|
275
|
-
//
|
|
275
|
+
// Prevent path traversal; only allow reading files within the current pipeline's artifact directory.
|
|
276
276
|
if (targetAbs !== rootAbs && !targetAbs.startsWith(`${rootAbs}${node_path_1.sep}`))
|
|
277
277
|
return null;
|
|
278
278
|
return targetAbs;
|
|
@@ -293,7 +293,7 @@ const readStoredArtifactContent = async (definition, relativePath) => {
|
|
|
293
293
|
parsed = null;
|
|
294
294
|
}
|
|
295
295
|
const parsedObj = toRecord(parsed);
|
|
296
|
-
//
|
|
296
|
+
// Prefer dispatching by kind field; fall back to the old heuristic when kind is absent (backward compatible)
|
|
297
297
|
const kind = typeof parsedObj?.kind === "string" ? parsedObj.kind : null;
|
|
298
298
|
const isEnvelope = kind === "envelope" || (!kind && parsedObj && "envelope" in parsedObj);
|
|
299
299
|
const envelopeObj = isEnvelope ? toRecord(parsedObj?.envelope) : null;
|
|
@@ -307,7 +307,7 @@ const readStoredArtifactContent = async (definition, relativePath) => {
|
|
|
307
307
|
.map((item) => toRecord(item)?.content)
|
|
308
308
|
.filter((item) => item !== undefined);
|
|
309
309
|
const logs = Array.isArray(envelopeObj.logs) ? envelopeObj.logs : [];
|
|
310
|
-
// envelope
|
|
310
|
+
// envelope preview returns contents + logs for easier troubleshooting.
|
|
311
311
|
return {
|
|
312
312
|
contents,
|
|
313
313
|
logs,
|
|
@@ -325,7 +325,7 @@ const readStoredArtifactContent = async (definition, relativePath) => {
|
|
|
325
325
|
};
|
|
326
326
|
exports.readStoredArtifactContent = readStoredArtifactContent;
|
|
327
327
|
const exportStoredArtifactContents = async (definitions, options) => {
|
|
328
|
-
//
|
|
328
|
+
// Prefer index for candidate files to reduce full scan; listStoredArtifacts auto-falls-back to scan when index is missing
|
|
329
329
|
const limit = Number.isFinite(options?.limit) ? Math.max(1, Math.trunc(options?.limit)) : 20000;
|
|
330
330
|
const listResult = await (0, exports.listStoredArtifacts)(definitions, {
|
|
331
331
|
pipelineIds: options?.pipelineIds,
|
|
@@ -357,8 +357,8 @@ const exportStoredArtifactContents = async (definitions, options) => {
|
|
|
357
357
|
out[dateKey][pipelineKey] = {};
|
|
358
358
|
if (!out[dateKey][pipelineKey][nodeKey])
|
|
359
359
|
out[dateKey][pipelineKey][nodeKey] = [];
|
|
360
|
-
//
|
|
361
|
-
//
|
|
360
|
+
// Export keeps only artifact content; no runId, filename or other metadata.
|
|
361
|
+
// If content is itself an array, flatten it to avoid "array inside array" in the export result.
|
|
362
362
|
if (Array.isArray(content.content)) {
|
|
363
363
|
out[dateKey][pipelineKey][nodeKey].push(...content.content);
|
|
364
364
|
}
|
|
@@ -18,7 +18,7 @@ const normalizePipelineSelector = (selector) => {
|
|
|
18
18
|
const requirePipelineId = (selector) => {
|
|
19
19
|
if (selector.pipelineId)
|
|
20
20
|
return selector.pipelineId;
|
|
21
|
-
//
|
|
21
|
+
// The embedded runtime service can only locate a pipeline instance by pipelineId; runId/batchRunId at this layer is only used for secondary matching.
|
|
22
22
|
throw new errors_1.CliError("Missing pipelineId for local runtime selector", {
|
|
23
23
|
code: "INVALID_ARGUMENT",
|
|
24
24
|
exitCode: 2,
|
|
@@ -64,7 +64,7 @@ const buildCliAppContext = (appContext) => {
|
|
|
64
64
|
runPipeline: async (pipelineId) => writableServices.pipeline.runPipeline(pipelineId),
|
|
65
65
|
retryNode: async (input) => writableServices.pipeline.retryNode(input),
|
|
66
66
|
diagnoseNode: async (input) => {
|
|
67
|
-
const runtimeApiClient = (0, server_runtime_client_1.
|
|
67
|
+
const runtimeApiClient = (0, server_runtime_client_1.createPipelineRuntimeApiClientWs)();
|
|
68
68
|
return runtimeApiClient.diagnoseNode(input.pipelineId, input.nodeId, input.itemKey);
|
|
69
69
|
},
|
|
70
70
|
getOutput: async (pipelineId, runId) => writableServices.pipeline.getOutput(pipelineId, runId),
|
|
@@ -127,7 +127,7 @@ const buildCliAppContext = (appContext) => {
|
|
|
127
127
|
};
|
|
128
128
|
};
|
|
129
129
|
const buildRuntimeApiOnlyContext = () => {
|
|
130
|
-
const runtimeApiClient = (0, server_runtime_client_1.
|
|
130
|
+
const runtimeApiClient = (0, server_runtime_client_1.createPipelineRuntimeApiClientWs)();
|
|
131
131
|
const serverLifecycleClient = (0, server_runtime_client_1.createServerLifecycleClient)();
|
|
132
132
|
const unsupported = async () => {
|
|
133
133
|
throw new Error("unsupported_cli_runtime_api_context");
|
|
@@ -187,10 +187,10 @@ const createMainCliBootstrap = () => {
|
|
|
187
187
|
return async ({ route }) => {
|
|
188
188
|
const bootstrap = route.bootstrap;
|
|
189
189
|
if (bootstrap?.runtimeApiOnly) {
|
|
190
|
-
// runtime-api
|
|
190
|
+
// runtime-api-only routes must reuse the daemon-provided API semantics to avoid incorrectly falling to the embedded service path.
|
|
191
191
|
if (bootstrap.ensureServerReady) {
|
|
192
192
|
const serverLifecycleClient = (0, server_runtime_client_1.createServerLifecycleClient)();
|
|
193
|
-
//
|
|
193
|
+
// Run-class commands must bind to a persistent execution host first, to avoid the CLI temporary process inadvertently acting as the daemon host.
|
|
194
194
|
await serverLifecycleClient.ensureServerReady();
|
|
195
195
|
}
|
|
196
196
|
return {
|
|
@@ -207,12 +207,12 @@ const createMainCliBootstrap = () => {
|
|
|
207
207
|
await appContext.gateway.connect();
|
|
208
208
|
}
|
|
209
209
|
else if (bootstrap?.gateway === "warmup") {
|
|
210
|
-
//
|
|
210
|
+
// For system snapshots, try connecting first; fall back to cached state on failure so read-only commands aren't completely blocked.
|
|
211
211
|
try {
|
|
212
212
|
await appContext.gateway.connect();
|
|
213
213
|
}
|
|
214
214
|
catch {
|
|
215
|
-
//
|
|
215
|
+
// Allow read-only commands to degrade gracefully.
|
|
216
216
|
}
|
|
217
217
|
}
|
|
218
218
|
return {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.agentRoutes = exports.agentSendCommand = exports.agentSessionCommand = exports.agentListCommand = void 0;
|
|
4
4
|
const errors_1 = require("../errors");
|
|
5
|
+
const i18n_1 = require("../i18n");
|
|
5
6
|
const readFlagAsPositiveInteger = (value, fallback) => {
|
|
6
7
|
if (typeof value !== "string")
|
|
7
8
|
return fallback;
|
|
@@ -84,42 +85,42 @@ exports.agentRoutes = [
|
|
|
84
85
|
{
|
|
85
86
|
key: "agent.list",
|
|
86
87
|
path: ["agent", "list"],
|
|
87
|
-
description: "
|
|
88
|
+
description: (0, i18n_1.t)("agent.list.description"),
|
|
88
89
|
handler: exports.agentListCommand,
|
|
89
90
|
bootstrap: { gateway: "required" },
|
|
90
91
|
help: {
|
|
91
92
|
usage: "taskmeld agent list [--format <json|md>]",
|
|
92
|
-
summary: "
|
|
93
|
+
summary: (0, i18n_1.t)("agent.list.summary"),
|
|
93
94
|
},
|
|
94
95
|
},
|
|
95
96
|
{
|
|
96
97
|
key: "agent.session",
|
|
97
98
|
path: ["agent", "session"],
|
|
98
|
-
description: "
|
|
99
|
+
description: (0, i18n_1.t)("agent.session.description"),
|
|
99
100
|
handler: exports.agentSessionCommand,
|
|
100
101
|
bootstrap: { gateway: "required" },
|
|
101
102
|
help: {
|
|
102
103
|
usage: "taskmeld agent session [agentId] [--format <json|md>]",
|
|
103
|
-
summary: "
|
|
104
|
-
args: [{ name: "agentId", required: false, description:
|
|
104
|
+
summary: (0, i18n_1.t)("agent.session.summary"),
|
|
105
|
+
args: [{ name: "agentId", required: false, description: (0, i18n_1.t)("agent.session.argAgentId") }],
|
|
105
106
|
},
|
|
106
107
|
},
|
|
107
108
|
{
|
|
108
109
|
key: "agent.send",
|
|
109
110
|
path: ["agent", "send"],
|
|
110
|
-
description: "
|
|
111
|
+
description: (0, i18n_1.t)("agent.send.description"),
|
|
111
112
|
handler: exports.agentSendCommand,
|
|
112
113
|
bootstrap: { gateway: "required" },
|
|
113
114
|
help: {
|
|
114
115
|
usage: "taskmeld agent send <agentId> <message> [--session <id>] [--format <json|md>]",
|
|
115
|
-
summary: "
|
|
116
|
+
summary: (0, i18n_1.t)("agent.send.summary"),
|
|
116
117
|
args: [
|
|
117
|
-
{ name: "agentId", required: true, description:
|
|
118
|
-
{ name: "message", required: true, description: "
|
|
118
|
+
{ name: "agentId", required: true, description: (0, i18n_1.t)("agent.send.argAgentId") },
|
|
119
|
+
{ name: "message", required: true, description: (0, i18n_1.t)("agent.send.argMessage") },
|
|
119
120
|
],
|
|
120
121
|
options: [
|
|
121
|
-
{ flags: ["--session"], valueName: "id", description:
|
|
122
|
-
{ flags: ["--stream"], description: "
|
|
122
|
+
{ flags: ["--session"], valueName: "id", description: (0, i18n_1.t)("agent.send.optSession") },
|
|
123
|
+
{ flags: ["--stream"], description: (0, i18n_1.t)("agent.send.optStream") },
|
|
123
124
|
],
|
|
124
125
|
},
|
|
125
126
|
},
|