hoi4-agent-tools 3.0.9 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -1
- package/README.md +4 -0
- package/dist/bin/stdio.js +23 -12
- package/dist/hoi4_agent_tools/core/analysis-checkpoints.js +82 -0
- package/dist/hoi4_agent_tools/core/artifact-resource.js +173 -0
- package/dist/hoi4_agent_tools/core/artifacts.js +84 -2
- package/dist/hoi4_agent_tools/core/dependency-pages.js +216 -0
- package/dist/hoi4_agent_tools/core/dependency-walk.js +266 -0
- package/dist/hoi4_agent_tools/core/domain-validation.js +188 -0
- package/dist/hoi4_agent_tools/core/engine.js +48 -5
- package/dist/hoi4_agent_tools/core/helper-expansion.js +186 -0
- package/dist/hoi4_agent_tools/core/index-segments.js +137 -0
- package/dist/hoi4_agent_tools/core/index.js +47 -4
- package/dist/hoi4_agent_tools/core/job-controls.js +109 -0
- package/dist/hoi4_agent_tools/core/job-executor.js +190 -0
- package/dist/hoi4_agent_tools/core/job-service.js +112 -0
- package/dist/hoi4_agent_tools/core/job-store.js +641 -0
- package/dist/hoi4_agent_tools/core/job-transactions.js +126 -0
- package/dist/hoi4_agent_tools/core/job-worker-host.js +200 -0
- package/dist/hoi4_agent_tools/core/job-worker.js +62 -0
- package/dist/hoi4_agent_tools/core/operation-context.js +31 -0
- package/dist/hoi4_agent_tools/core/operation-result.js +412 -0
- package/dist/hoi4_agent_tools/core/operation-tasks.js +382 -0
- package/dist/hoi4_agent_tools/core/persistent-analysis-cache.js +383 -0
- package/dist/hoi4_agent_tools/core/result.js +1 -0
- package/dist/hoi4_agent_tools/core/scanner.js +15 -75
- package/dist/hoi4_agent_tools/core/semantic-dependencies.js +233 -0
- package/dist/hoi4_agent_tools/core/shared-request-capacity.js +43 -11
- package/dist/hoi4_agent_tools/core/source/cache.js +107 -0
- package/dist/hoi4_agent_tools/core/source/parser.js +11 -1
- package/dist/hoi4_agent_tools/core/transaction-execution.js +134 -0
- package/dist/hoi4_agent_tools/core/transactions.js +80 -27
- package/dist/hoi4_agent_tools/event/graph.js +214 -290
- package/dist/hoi4_agent_tools/event/helper-expansion.js +33 -0
- package/dist/hoi4_agent_tools/event/job-operations.js +51 -0
- package/dist/hoi4_agent_tools/event/operations.js +179 -0
- package/dist/hoi4_agent_tools/event/service.js +67 -15
- package/dist/hoi4_agent_tools/event/source-analysis.js +5 -4
- package/dist/hoi4_agent_tools/focus/job-operations.js +77 -0
- package/dist/hoi4_agent_tools/focus/tool-operations.js +669 -0
- package/dist/hoi4_agent_tools/focus/tool-support.js +123 -0
- package/dist/hoi4_agent_tools/gui/job-operations.js +81 -0
- package/dist/hoi4_agent_tools/gui/tool-operations.js +270 -0
- package/dist/hoi4_agent_tools/map/job-operations.js +76 -0
- package/dist/hoi4_agent_tools/map/tool-operations.js +493 -0
- package/dist/hoi4_agent_tools/mcp/prompts/probability.js +25 -0
- package/dist/hoi4_agent_tools/mcp/prompts/register-modern.js +29 -0
- package/dist/hoi4_agent_tools/mcp/resources/register-modern.js +34 -0
- package/dist/hoi4_agent_tools/mcp/resources/register.js +4 -150
- package/dist/hoi4_agent_tools/mcp/server/base-tools.js +3 -219
- package/dist/hoi4_agent_tools/mcp/server/create-modern-operations.js +188 -0
- package/dist/hoi4_agent_tools/mcp/server/create.js +16 -6
- package/dist/hoi4_agent_tools/mcp/server/instructions.js +2 -0
- package/dist/hoi4_agent_tools/mcp/server/job-task-store.js +67 -0
- package/dist/hoi4_agent_tools/mcp/server/legacy-task-tools.js +20 -0
- package/dist/hoi4_agent_tools/mcp/server/modern-tasks.js +168 -0
- package/dist/hoi4_agent_tools/mcp/server/progress.js +17 -15
- package/dist/hoi4_agent_tools/mcp/server/request-lifecycle.js +43 -0
- package/dist/hoi4_agent_tools/mcp/server/result.js +2 -412
- package/dist/hoi4_agent_tools/mcp/server/task-request-context.js +9 -0
- package/dist/hoi4_agent_tools/mcp/server/task-tool-catalog.js +14 -0
- package/dist/hoi4_agent_tools/mcp/server/task-tool-definition.js +1 -0
- package/dist/hoi4_agent_tools/mcp/server/transaction-execution.js +1 -123
- package/dist/hoi4_agent_tools/mcp/tools/chaosx.js +38 -21
- package/dist/hoi4_agent_tools/mcp/tools/event.js +24 -315
- package/dist/hoi4_agent_tools/mcp/tools/focus.js +34 -1039
- package/dist/hoi4_agent_tools/mcp/tools/gui.js +19 -504
- package/dist/hoi4_agent_tools/mcp/tools/job.js +46 -0
- package/dist/hoi4_agent_tools/mcp/tools/map.js +21 -600
- package/dist/hoi4_agent_tools/mcp/tools/probability.js +39 -463
- package/dist/hoi4_agent_tools/mcp/tools/technology.js +40 -304
- package/dist/hoi4_agent_tools/mcp/transports/http.js +58 -1
- package/dist/hoi4_agent_tools/mcp/transports/modern-task-routing.js +109 -0
- package/dist/hoi4_agent_tools/mcp/transports/negotiated-stdio.js +121 -0
- package/dist/hoi4_agent_tools/probability/job-operations.js +58 -0
- package/dist/hoi4_agent_tools/probability/operations.js +183 -0
- package/dist/hoi4_agent_tools/probability/service.js +24 -3
- package/dist/hoi4_agent_tools/schemas/common.js +2 -0
- package/dist/hoi4_agent_tools/schemas/event.js +72 -1
- package/dist/hoi4_agent_tools/schemas/focus-requests.js +189 -0
- package/dist/hoi4_agent_tools/schemas/gui-requests.js +193 -0
- package/dist/hoi4_agent_tools/schemas/helper-expansion.js +58 -0
- package/dist/hoi4_agent_tools/schemas/map-requests.js +85 -0
- package/dist/hoi4_agent_tools/schemas/probability-requests.js +168 -0
- package/dist/hoi4_agent_tools/schemas/technology.js +90 -1
- package/dist/hoi4_agent_tools/schemas/transaction.js +1 -0
- package/dist/hoi4_agent_tools/technology/graph.js +83 -78
- package/dist/hoi4_agent_tools/technology/helper-expansion.js +33 -0
- package/dist/hoi4_agent_tools/technology/job-operations.js +60 -0
- package/dist/hoi4_agent_tools/technology/operations.js +125 -0
- package/dist/hoi4_agent_tools/technology/service.js +89 -15
- package/dist/hoi4_agent_tools/technology/source-analysis.js +2 -1
- package/dist/hoi4_agent_tools/version.js +1 -1
- package/docs/README.md +2 -0
- package/docs/development.md +2 -0
- package/docs/events.md +7 -2
- package/docs/helper-expansion.md +78 -0
- package/docs/http.md +2 -0
- package/docs/jobs.md +85 -0
- package/docs/technology.md +6 -2
- package/package.json +11 -5
- package/schemas/configuration.schema.json +1 -1
- package/schemas/continuous-focus-palette.schema.json +1 -1
- package/schemas/custom-weighted-pool.schema.json +1 -1
- package/schemas/event-feature-manifest.schema.json +1 -1
- package/schemas/focus-plan.schema.json +1 -1
- package/schemas/focus-planning-sidecar.schema.json +1 -1
- package/schemas/gui-animation-source.schema.json +1 -1
- package/schemas/gui-helper.schema.json +1 -1
- package/schemas/gui-scenario.schema.json +1 -1
- package/schemas/helper-expansion-request.schema.json +44 -0
- package/schemas/helper-expansion-summary.schema.json +70 -0
- package/schemas/map-operation.schema.json +1 -1
- package/schemas/operation-result.schema.json +1 -1
- package/schemas/probability-analysis-result.schema.json +1 -1
- package/schemas/probability-compare-input.schema.json +1 -1
- package/schemas/probability-evaluate-input.schema.json +1 -1
- package/schemas/probability-inspect-input.schema.json +1 -1
- package/schemas/probability-render-input.schema.json +1 -1
- package/schemas/probability-scenario-set.schema.json +1 -1
- package/schemas/probability-sequence-input.schema.json +1 -1
- package/schemas/probability-simulate-input.schema.json +1 -1
- package/schemas/probability-sweep-input.schema.json +1 -1
- package/server.json +2 -2
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { compareCodeUnits, hashCanonical } from './canonical.js';
|
|
2
2
|
import { SymbolIndex } from './index.js';
|
|
3
|
+
import { IndexSegmentCache } from './index-segments.js';
|
|
4
|
+
import { PersistentAnalysisCache, } from './persistent-analysis-cache.js';
|
|
5
|
+
import { sourceDocuments } from './source/cache.js';
|
|
3
6
|
import { WorkspaceScanner } from './scanner.js';
|
|
4
7
|
import { ArtifactStore } from './artifacts.js';
|
|
5
|
-
import { TransactionManager } from './transactions.js';
|
|
8
|
+
import { TransactionManager, transactionRootFingerprint } from './transactions.js';
|
|
6
9
|
import { RequestScheduler } from './request-scheduler.js';
|
|
7
10
|
import { SharedRequestCapacity } from './shared-request-capacity.js';
|
|
11
|
+
import { JobStore } from './job-store.js';
|
|
8
12
|
const RECOVERY_TTL_SECONDS = 3_600;
|
|
9
13
|
const RECOVERY_MAX_BYTES = 536_870_912;
|
|
10
14
|
const RECOVERY_MAX_RECORDS = 128;
|
|
@@ -81,6 +85,8 @@ export class CoreEngine {
|
|
|
81
85
|
scanner;
|
|
82
86
|
artifacts;
|
|
83
87
|
transactions;
|
|
88
|
+
indexSegments;
|
|
89
|
+
persistentAnalysisCache;
|
|
84
90
|
#scanCache = new Map();
|
|
85
91
|
#scanFlights = new Map();
|
|
86
92
|
#scanGenerations = new Map();
|
|
@@ -88,14 +94,30 @@ export class CoreEngine {
|
|
|
88
94
|
#activeScanAdmissions = new Set();
|
|
89
95
|
constructor(resolver, services = {}) {
|
|
90
96
|
this.resolver = resolver;
|
|
97
|
+
this.indexSegments = services.indexSegments ?? new IndexSegmentCache();
|
|
98
|
+
const state = resolver.serverState();
|
|
99
|
+
this.persistentAnalysisCache =
|
|
100
|
+
services.persistentAnalysisCache === null || state === undefined
|
|
101
|
+
? undefined
|
|
102
|
+
: Promise.resolve(services.persistentAnalysisCache ?? PersistentAnalysisCache.create(state));
|
|
91
103
|
this.requests = new RequestScheduler(resolver.config().maxConcurrentTools);
|
|
92
104
|
this.sharedRequests = new SharedRequestCapacity(resolver.serverState()?.root, resolver.config().maxSharedTools);
|
|
93
105
|
this.scanner =
|
|
94
106
|
services.scanner ??
|
|
95
107
|
new WorkspaceScanner(resolver.config().scanMaxFiles, resolver.config().scanMaxBytes, resolver.config().scanMaxFileBytes);
|
|
108
|
+
let retentionJobs;
|
|
96
109
|
this.artifacts =
|
|
97
110
|
services.artifacts ??
|
|
98
|
-
new ArtifactStore(resolver.config().artifactMaxBytes, resolver.config().artifactMaxEntries, resolver.config().artifactMaxSingleBytes
|
|
111
|
+
new ArtifactStore(resolver.config().artifactMaxBytes, resolver.config().artifactMaxEntries, resolver.config().artifactMaxSingleBytes, state === undefined
|
|
112
|
+
? undefined
|
|
113
|
+
: async (workspace, signal) => {
|
|
114
|
+
retentionJobs ??= JobStore.create(state);
|
|
115
|
+
return (await retentionJobs).checkpointResources({
|
|
116
|
+
workspaceId: workspace.id,
|
|
117
|
+
workspaceIdentity: workspace.workspaceIdentity,
|
|
118
|
+
rootFingerprint: transactionRootFingerprint(workspace),
|
|
119
|
+
}, signal);
|
|
120
|
+
});
|
|
99
121
|
this.transactions =
|
|
100
122
|
services.transactions ??
|
|
101
123
|
new TransactionManager(resolver, this.artifacts, RECOVERY_TTL_SECONDS, RECOVERY_MAX_BYTES, RECOVERY_MAX_RECORDS, resolver.serverState());
|
|
@@ -160,12 +182,28 @@ export class CoreEngine {
|
|
|
160
182
|
signal: controller.signal,
|
|
161
183
|
});
|
|
162
184
|
controller.signal.throwIfAborted();
|
|
163
|
-
const
|
|
185
|
+
const persistentAnalysisCache = await this.persistentAnalysisCache;
|
|
186
|
+
const analysisScope = {
|
|
187
|
+
workspaceIdentity: workspace.workspaceIdentity,
|
|
188
|
+
rootFingerprint: transactionRootFingerprint(workspace),
|
|
189
|
+
principal: principal ?? null,
|
|
190
|
+
};
|
|
191
|
+
await persistentAnalysisCache?.hydrate(analysisScope, files, sourceDocuments, this.indexSegments, controller.signal);
|
|
192
|
+
const revision = hashCanonical(files.map(({ absolutePath, displayPath, relativePath, rootKind, loadOrder, shadowedBy, sha256, }) => ({
|
|
193
|
+
absolutePath,
|
|
194
|
+
displayPath,
|
|
195
|
+
relativePath,
|
|
196
|
+
rootKind,
|
|
197
|
+
loadOrder,
|
|
198
|
+
shadowedBy: shadowedBy ?? null,
|
|
199
|
+
sha256,
|
|
200
|
+
})));
|
|
164
201
|
const cacheKey = `${requestKey}:${revision}`;
|
|
165
202
|
const cached = this.#scanCache.get(cacheKey);
|
|
166
203
|
if (cached !== undefined)
|
|
167
204
|
return cached;
|
|
168
|
-
const index = await SymbolIndex.buildAsync(files, controller.signal);
|
|
205
|
+
const index = await SymbolIndex.buildAsync(files, controller.signal, this.indexSegments);
|
|
206
|
+
await persistentAnalysisCache?.persist(analysisScope, files, sourceDocuments, this.indexSegments, controller.signal);
|
|
169
207
|
const snapshot = {
|
|
170
208
|
workspaceId,
|
|
171
209
|
revision,
|
|
@@ -219,12 +257,17 @@ export class CoreEngine {
|
|
|
219
257
|
/** Drop completed scan buffers without invalidating revisions or interrupting active scans. */
|
|
220
258
|
releaseScanCaches() {
|
|
221
259
|
this.#scanCache.clear();
|
|
260
|
+
this.indexSegments.clear();
|
|
261
|
+
sourceDocuments.clear();
|
|
222
262
|
this.scanner.clearCaches();
|
|
223
263
|
}
|
|
224
264
|
/** Monotonic cache generation used by domain services to invalidate derived snapshots. */
|
|
225
265
|
generation(workspaceId) {
|
|
226
266
|
return this.#scanGenerations.get(workspaceId) ?? 0;
|
|
227
267
|
}
|
|
268
|
+
async persistentAnalysisCacheStatistics() {
|
|
269
|
+
return (await this.persistentAnalysisCache)?.statistics();
|
|
270
|
+
}
|
|
228
271
|
awaitScanFlight(requestKey, flight, signal) {
|
|
229
272
|
signal?.throwIfAborted();
|
|
230
273
|
flight.waiters += 1;
|
|
@@ -342,6 +385,6 @@ export class CoreEngine {
|
|
|
342
385
|
}
|
|
343
386
|
}
|
|
344
387
|
indexFiles(files) {
|
|
345
|
-
return SymbolIndex.build(files);
|
|
388
|
+
return SymbolIndex.build(files, this.indexSegments);
|
|
346
389
|
}
|
|
347
390
|
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
import { PACKAGE_VERSION } from '../version.js';
|
|
3
|
+
import { helperExpansionRequestSchema, } from '../schemas/helper-expansion.js';
|
|
4
|
+
import { canonicalJson, compareCodeUnits, hashCanonical } from './canonical.js';
|
|
5
|
+
import { boundedSourceHashEvidence } from './artifacts.js';
|
|
6
|
+
import { DependencyPages, dependencyPageCursorSchema } from './dependency-pages.js';
|
|
7
|
+
import { ServiceError } from './result.js';
|
|
8
|
+
import { transactionRootFingerprint } from './transactions.js';
|
|
9
|
+
const digest = z.string().regex(/^[a-f0-9]{64}$/u);
|
|
10
|
+
const querySchema = helperExpansionRequestSchema
|
|
11
|
+
.pick({ rootIds: true })
|
|
12
|
+
.extend({ maxDepth: z.number().int().min(1).max(256) });
|
|
13
|
+
const continuationSchema = z
|
|
14
|
+
.object({
|
|
15
|
+
version: z.literal(1),
|
|
16
|
+
toolVersion: z.string(),
|
|
17
|
+
domain: z.enum(['event', 'technology']),
|
|
18
|
+
scope: digest,
|
|
19
|
+
sourceRevision: digest,
|
|
20
|
+
query: querySchema,
|
|
21
|
+
cursor: dependencyPageCursorSchema,
|
|
22
|
+
})
|
|
23
|
+
.strict();
|
|
24
|
+
export function helperExpansionValidation(summary) {
|
|
25
|
+
return {
|
|
26
|
+
passed: summary.complete,
|
|
27
|
+
checks: [
|
|
28
|
+
{
|
|
29
|
+
id: 'helper-expansion-coverage',
|
|
30
|
+
passed: summary.complete,
|
|
31
|
+
message: `${summary.completedRoots}/${summary.totalRoots} roots finished; ${summary.depthStops} depth stops; source inventory ${summary.sourceComplete ? 'complete' : 'partial'}; static source coverage only, not runtime validity`,
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** Read-only structural evidence, with authenticated opaque continuation resources shared by both domains. */
|
|
37
|
+
export async function inspectHelperExpansion(engine, workspaceId, inventory, request = {}, principal, signal) {
|
|
38
|
+
signal?.throwIfAborted();
|
|
39
|
+
const options = helperExpansionRequestSchema.parse(request);
|
|
40
|
+
const workspace = engine.resolver.get(workspaceId, principal);
|
|
41
|
+
const scope = hashCanonical({
|
|
42
|
+
workspaceId: workspace.id,
|
|
43
|
+
workspaceIdentity: workspace.workspaceIdentity,
|
|
44
|
+
ownerIdentity: workspace.ownerIdentity,
|
|
45
|
+
roots: transactionRootFingerprint(workspace),
|
|
46
|
+
principal: principal ?? null,
|
|
47
|
+
});
|
|
48
|
+
let restored;
|
|
49
|
+
if (options.continuationUri !== undefined) {
|
|
50
|
+
const resource = await engine.artifacts.readLogical(workspace, options.continuationUri, { mimeType: 'application/json', maxBytes: 8_388_608, maxChunks: 8_192 }, signal);
|
|
51
|
+
try {
|
|
52
|
+
restored = continuationSchema.parse(JSON.parse(resource.bytes.toString('utf8')));
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
throw new ServiceError('HELPER_CONTINUATION_INVALID', 'Helper continuation does not contain a valid bounded traversal envelope');
|
|
56
|
+
}
|
|
57
|
+
if (restored.domain !== inventory.domain || restored.scope !== scope)
|
|
58
|
+
throw new ServiceError('HELPER_CONTINUATION_SCOPE_MISMATCH', 'Helper continuation belongs to a different domain, workspace, root topology, or principal');
|
|
59
|
+
if (restored.toolVersion !== PACKAGE_VERSION ||
|
|
60
|
+
restored.sourceRevision !== inventory.sourceRevision)
|
|
61
|
+
throw new ServiceError('HELPER_CONTINUATION_STALE', 'Helper continuation requires the same tool version and exact source revision; start a new analysis');
|
|
62
|
+
}
|
|
63
|
+
const rootIds = options.rootIds === undefined
|
|
64
|
+
? restored?.query.rootIds
|
|
65
|
+
: [...new Set(options.rootIds)].sort(compareCodeUnits);
|
|
66
|
+
const query = querySchema.parse({
|
|
67
|
+
...(rootIds === undefined ? {} : { rootIds }),
|
|
68
|
+
maxDepth: options.maxDepth ?? restored?.query.maxDepth ?? 64,
|
|
69
|
+
});
|
|
70
|
+
if (restored !== undefined && hashCanonical(restored.query) !== hashCanonical(query))
|
|
71
|
+
throw new ServiceError('HELPER_CONTINUATION_QUERY_MISMATCH', 'Helper continuation cannot change root selection or depth; start a new analysis');
|
|
72
|
+
const knownRoots = new Set(inventory.roots);
|
|
73
|
+
if (query.rootIds?.some((id) => !knownRoots.has(id)) === true)
|
|
74
|
+
throw new ServiceError('HELPER_ROOT_UNKNOWN', 'Every selected root must be a structural helper-call root in the current source inventory');
|
|
75
|
+
const input = {
|
|
76
|
+
domain: `${inventory.domain}-helper-expansion.v1`,
|
|
77
|
+
sourceRevision: inventory.sourceRevision,
|
|
78
|
+
edges: inventory.edges.map(({ id, from, to }) => ({ id, from, to })),
|
|
79
|
+
roots: query.rootIds ?? [...inventory.roots].sort(compareCodeUnits),
|
|
80
|
+
branches: inventory.branches,
|
|
81
|
+
maximumDepth: query.maxDepth,
|
|
82
|
+
visitPolicy: 'edge_path',
|
|
83
|
+
reverseEdges: false,
|
|
84
|
+
};
|
|
85
|
+
const pager = new DependencyPages(input, restored?.cursor);
|
|
86
|
+
const page = pager.page(options.maxRecords ?? 250, options.maxWork ?? 5_000, signal);
|
|
87
|
+
const summary = {
|
|
88
|
+
complete: inventory.sourceComplete && page.finished && page.cursor.depthStops === 0,
|
|
89
|
+
finished: page.finished,
|
|
90
|
+
sourceComplete: inventory.sourceComplete,
|
|
91
|
+
maxDepth: query.maxDepth,
|
|
92
|
+
records: page.records.length,
|
|
93
|
+
totalRecords: page.cursor.emitted,
|
|
94
|
+
work: page.work,
|
|
95
|
+
totalWork: page.cursor.steps,
|
|
96
|
+
completedRoots: page.completedRoots,
|
|
97
|
+
totalRoots: page.totalRoots,
|
|
98
|
+
cycles: page.cursor.cycles,
|
|
99
|
+
depthStops: page.cursor.depthStops,
|
|
100
|
+
};
|
|
101
|
+
const used = new Set(page.records.flatMap(({ path }) => path));
|
|
102
|
+
const records = page.records.map(({ kind, root, path }) => ({
|
|
103
|
+
id: hashCanonical({ key: pager.key, kind, root, path }),
|
|
104
|
+
kind,
|
|
105
|
+
rootId: input.roots[root],
|
|
106
|
+
path: path.map((index) => inventory.edges[index].id),
|
|
107
|
+
}));
|
|
108
|
+
const continuationName = `${inventory.domain}-helper-continuation.json`;
|
|
109
|
+
const report = {
|
|
110
|
+
schemaVersion: 'helper-expansion.v1',
|
|
111
|
+
domain: inventory.domain,
|
|
112
|
+
workspaceId: workspace.id,
|
|
113
|
+
workspaceIdentity: workspace.workspaceIdentity,
|
|
114
|
+
sourceRevision: inventory.sourceRevision,
|
|
115
|
+
sourceEvidence: boundedSourceHashEvidence(inventory.sourceHashes),
|
|
116
|
+
unresolved: {
|
|
117
|
+
count: inventory.unresolved?.length ?? 0,
|
|
118
|
+
retainedCount: Math.min(100, inventory.unresolved?.length ?? 0),
|
|
119
|
+
sample: (inventory.unresolved ?? []).slice(0, 100),
|
|
120
|
+
},
|
|
121
|
+
query,
|
|
122
|
+
coverage: summary,
|
|
123
|
+
analysisBoundary: {
|
|
124
|
+
staticAnalysis: true,
|
|
125
|
+
visitPolicy: 'edge_path',
|
|
126
|
+
cyclePolicy: 'report_and_stop',
|
|
127
|
+
conditions: 'source evidence only; not evaluated',
|
|
128
|
+
directReferences: 'helper-owned leaves only; use the ordinary inspection modes for non-helper references',
|
|
129
|
+
completeness: 'recognized static call paths under report-and-stop cycle semantics; not runtime outcomes or resolution of dynamic source findings',
|
|
130
|
+
},
|
|
131
|
+
records,
|
|
132
|
+
edges: [...used].sort((left, right) => left - right).map((index) => inventory.edges[index]),
|
|
133
|
+
...(page.finished
|
|
134
|
+
? {}
|
|
135
|
+
: {
|
|
136
|
+
continuation: {
|
|
137
|
+
name: continuationName,
|
|
138
|
+
mimeType: 'application/json',
|
|
139
|
+
resolution: 'helperExpansion.continuationUri in the tool result; also linked as a sibling artifact',
|
|
140
|
+
},
|
|
141
|
+
}),
|
|
142
|
+
};
|
|
143
|
+
const reportJson = `${canonicalJson(report)}\n`;
|
|
144
|
+
const provenance = {
|
|
145
|
+
kind: `${inventory.domain}-helper-expansion`,
|
|
146
|
+
toolVersion: PACKAGE_VERSION,
|
|
147
|
+
schemaVersion: '1',
|
|
148
|
+
sourceHashes: {},
|
|
149
|
+
metadata: { scope, sourceRevision: inventory.sourceRevision, traversal: pager.key },
|
|
150
|
+
};
|
|
151
|
+
const writes = [
|
|
152
|
+
{
|
|
153
|
+
name: `${inventory.domain}-helper-expansion.json`,
|
|
154
|
+
mimeType: 'application/json',
|
|
155
|
+
content: reportJson,
|
|
156
|
+
provenance,
|
|
157
|
+
description: 'Bounded source-linked helper path evidence and explicit traversal coverage',
|
|
158
|
+
},
|
|
159
|
+
];
|
|
160
|
+
if (!page.finished)
|
|
161
|
+
writes.push({
|
|
162
|
+
name: continuationName,
|
|
163
|
+
mimeType: 'application/json',
|
|
164
|
+
content: canonicalJson(continuationSchema.parse({
|
|
165
|
+
version: 1,
|
|
166
|
+
toolVersion: PACKAGE_VERSION,
|
|
167
|
+
domain: inventory.domain,
|
|
168
|
+
scope,
|
|
169
|
+
sourceRevision: inventory.sourceRevision,
|
|
170
|
+
query,
|
|
171
|
+
cursor: page.cursor,
|
|
172
|
+
})),
|
|
173
|
+
provenance,
|
|
174
|
+
description: 'Opaque revision- and principal-bound helper continuation; pass as helperExpansion.continuationUri',
|
|
175
|
+
});
|
|
176
|
+
const artifacts = await engine.artifacts.withAtomicChunkedWrites(workspace, writes, (stored) => Promise.resolve([...stored]), signal);
|
|
177
|
+
return {
|
|
178
|
+
report,
|
|
179
|
+
reportJson,
|
|
180
|
+
summary: {
|
|
181
|
+
...summary,
|
|
182
|
+
...(artifacts[1] === undefined ? {} : { continuationUri: artifacts[1].uri }),
|
|
183
|
+
},
|
|
184
|
+
artifacts,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { canonicalJson, compareCodeUnits, hashCanonical } from './canonical.js';
|
|
2
|
+
import { PACKAGE_VERSION } from '../version.js';
|
|
3
|
+
export function indexSegmentAddress(file, isProvinceDefinition) {
|
|
4
|
+
return hashCanonical({
|
|
5
|
+
version: 'file-index-segment.v1',
|
|
6
|
+
toolVersion: PACKAGE_VERSION,
|
|
7
|
+
sourceHash: file.sha256,
|
|
8
|
+
path: file.displayPath,
|
|
9
|
+
relativePath: file.relativePath,
|
|
10
|
+
rootKind: file.rootKind,
|
|
11
|
+
loadOrder: file.loadOrder,
|
|
12
|
+
shadowedBy: file.shadowedBy ?? null,
|
|
13
|
+
isProvinceDefinition,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
/** Encoded entries keep the aggregate index's mutable records out of the shared cache. */
|
|
17
|
+
export class IndexSegmentCache {
|
|
18
|
+
maxBytes;
|
|
19
|
+
#entries = new Map();
|
|
20
|
+
#retainedBytes = 0;
|
|
21
|
+
#hits = 0;
|
|
22
|
+
#misses = 0;
|
|
23
|
+
#evictions = 0;
|
|
24
|
+
#oversized = 0;
|
|
25
|
+
constructor(maxBytes = 64 * 1024 * 1024) {
|
|
26
|
+
this.maxBytes = maxBytes;
|
|
27
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0)
|
|
28
|
+
throw new RangeError('Index segment cache size must be a non-negative integer');
|
|
29
|
+
}
|
|
30
|
+
get(address) {
|
|
31
|
+
const entry = this.#entries.get(address);
|
|
32
|
+
if (entry === undefined) {
|
|
33
|
+
this.#misses += 1;
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
this.#hits += 1;
|
|
37
|
+
this.#entries.delete(address);
|
|
38
|
+
this.#entries.set(address, entry);
|
|
39
|
+
return JSON.parse(entry.encoded);
|
|
40
|
+
}
|
|
41
|
+
/** Import one authenticated canonical entry without counting it as a process-local hit. */
|
|
42
|
+
importEncoded(address, encoded) {
|
|
43
|
+
const segment = JSON.parse(encoded);
|
|
44
|
+
if (typeof segment !== 'object' ||
|
|
45
|
+
segment === null ||
|
|
46
|
+
!('symbols' in segment) ||
|
|
47
|
+
!Array.isArray(segment.symbols) ||
|
|
48
|
+
!('references' in segment) ||
|
|
49
|
+
!Array.isArray(segment.references) ||
|
|
50
|
+
!('diagnostics' in segment) ||
|
|
51
|
+
!Array.isArray(segment.diagnostics) ||
|
|
52
|
+
!('complete' in segment) ||
|
|
53
|
+
typeof segment.complete !== 'boolean')
|
|
54
|
+
throw new Error('Persistent index segment is structurally invalid');
|
|
55
|
+
this.put(address, segment);
|
|
56
|
+
}
|
|
57
|
+
exportEncoded(address) {
|
|
58
|
+
return this.#entries.get(address)?.encoded;
|
|
59
|
+
}
|
|
60
|
+
put(address, segment) {
|
|
61
|
+
const encoded = canonicalJson(segment);
|
|
62
|
+
// Account for the UTF-16 string retained by V8, including conservative key overhead.
|
|
63
|
+
const bytes = encoded.length * 2 + address.length * 2 + 128;
|
|
64
|
+
if (bytes > this.maxBytes) {
|
|
65
|
+
this.#oversized += 1;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const previous = this.#entries.get(address);
|
|
69
|
+
if (previous !== undefined) {
|
|
70
|
+
if (previous.encoded !== encoded)
|
|
71
|
+
throw new Error('An immutable index segment address produced different file-local facts');
|
|
72
|
+
this.#entries.delete(address);
|
|
73
|
+
this.#entries.set(address, previous);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
while (this.#retainedBytes + bytes > this.maxBytes) {
|
|
77
|
+
const oldest = this.#entries.entries().next().value;
|
|
78
|
+
if (oldest === undefined)
|
|
79
|
+
break;
|
|
80
|
+
this.#entries.delete(oldest[0]);
|
|
81
|
+
this.#retainedBytes -= oldest[1].bytes;
|
|
82
|
+
this.#evictions += 1;
|
|
83
|
+
}
|
|
84
|
+
this.#entries.set(address, { encoded, bytes });
|
|
85
|
+
this.#retainedBytes += bytes;
|
|
86
|
+
}
|
|
87
|
+
clear() {
|
|
88
|
+
this.#entries.clear();
|
|
89
|
+
this.#retainedBytes = 0;
|
|
90
|
+
}
|
|
91
|
+
statistics() {
|
|
92
|
+
return {
|
|
93
|
+
hits: this.#hits,
|
|
94
|
+
misses: this.#misses,
|
|
95
|
+
evictions: this.#evictions,
|
|
96
|
+
oversized: this.#oversized,
|
|
97
|
+
retainedBytes: this.#retainedBytes,
|
|
98
|
+
retainedSegments: this.#entries.size,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/** Reverse edges are derived from typed source references, never matching arbitrary strings. */
|
|
103
|
+
export class ReverseSourceDependencies {
|
|
104
|
+
#consumers = new Map();
|
|
105
|
+
#definitions = new Map();
|
|
106
|
+
constructor(symbols, references) {
|
|
107
|
+
for (const symbol of symbols) {
|
|
108
|
+
const definitions = this.#definitions.get(symbol.path) ?? new Set();
|
|
109
|
+
definitions.add(`${symbol.kind}:${symbol.id}`);
|
|
110
|
+
this.#definitions.set(symbol.path, definitions);
|
|
111
|
+
}
|
|
112
|
+
for (const reference of references) {
|
|
113
|
+
const key = `${reference.toKind}:${reference.to}`;
|
|
114
|
+
const consumers = this.#consumers.get(key) ?? new Set();
|
|
115
|
+
consumers.add(reference.path);
|
|
116
|
+
this.#consumers.set(key, consumers);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
affectedFiles(changedFiles) {
|
|
120
|
+
const affected = new Set(changedFiles);
|
|
121
|
+
const queue = [...affected];
|
|
122
|
+
for (const source of queue) {
|
|
123
|
+
for (const definition of this.#definitions.get(source) ?? []) {
|
|
124
|
+
for (const consumer of this.#consumers.get(definition) ?? []) {
|
|
125
|
+
if (affected.has(consumer))
|
|
126
|
+
continue;
|
|
127
|
+
affected.add(consumer);
|
|
128
|
+
queue.push(consumer);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return [...affected].sort(compareCodeUnits);
|
|
133
|
+
}
|
|
134
|
+
consumers(kind, id) {
|
|
135
|
+
return [...(this.#consumers.get(`${kind}:${id}`) ?? [])].sort(compareCodeUnits);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { compareCodeUnits } from './canonical.js';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { sortDiagnostics } from './diagnostics.js';
|
|
4
|
+
import { indexSegmentAddress } from './index-segments.js';
|
|
4
5
|
import { parseClausewitz, parseLocalisation, assignments, childBlocks, firstScalar, locationFor, sourcePartialLimitDiagnostics, SOURCE_MAX_BYTES, SOURCE_MAX_NESTING, nodeLocation, } from './source/index.js';
|
|
5
6
|
const spriteDefinitionKeys = new Set([
|
|
6
7
|
'spritetype',
|
|
@@ -269,7 +270,8 @@ export class SymbolIndex {
|
|
|
269
270
|
#symbolLimitReported = false;
|
|
270
271
|
#referenceLimitReported = false;
|
|
271
272
|
#diagnosticsTruncated = false;
|
|
272
|
-
|
|
273
|
+
#indexTableBlocked = new Set();
|
|
274
|
+
static build(files, segments) {
|
|
273
275
|
const index = new SymbolIndex();
|
|
274
276
|
const definitionSelection = selectedDefinitionFiles(files);
|
|
275
277
|
for (const displayPath of definitionSelection.selected)
|
|
@@ -279,13 +281,13 @@ export class SymbolIndex {
|
|
|
279
281
|
}
|
|
280
282
|
for (const file of [...files].sort((a, b) => a.loadOrder - b.loadOrder || compareCodeUnits(a.displayPath, b.displayPath))) {
|
|
281
283
|
index.files.set(file.displayPath, file);
|
|
282
|
-
index.
|
|
284
|
+
index.indexFileWithSegments(file, segments);
|
|
283
285
|
}
|
|
284
286
|
index.finalize();
|
|
285
287
|
return index;
|
|
286
288
|
}
|
|
287
289
|
/** Yield between source files so protocol cancellation and progress remain responsive. */
|
|
288
|
-
static async buildAsync(files, signal) {
|
|
290
|
+
static async buildAsync(files, signal, segments) {
|
|
289
291
|
signal?.throwIfAborted();
|
|
290
292
|
const index = new SymbolIndex();
|
|
291
293
|
const definitionSelection = selectedDefinitionFiles(files);
|
|
@@ -298,7 +300,7 @@ export class SymbolIndex {
|
|
|
298
300
|
for (const file of [...files].sort((a, b) => a.loadOrder - b.loadOrder || compareCodeUnits(a.displayPath, b.displayPath))) {
|
|
299
301
|
signal?.throwIfAborted();
|
|
300
302
|
index.files.set(file.displayPath, file);
|
|
301
|
-
index.
|
|
303
|
+
index.indexFileWithSegments(file, segments);
|
|
302
304
|
if (performance.now() - yieldedAt >= 20) {
|
|
303
305
|
await new Promise((resolve) => setImmediate(resolve));
|
|
304
306
|
signal?.throwIfAborted();
|
|
@@ -435,6 +437,45 @@ export class SymbolIndex {
|
|
|
435
437
|
for (const diagnostic of diagnostics)
|
|
436
438
|
this.addDiagnostic(diagnostic);
|
|
437
439
|
}
|
|
440
|
+
indexFileWithSegments(file, cache) {
|
|
441
|
+
if (cache === undefined) {
|
|
442
|
+
this.indexFile(file);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
const address = indexSegmentAddress(file, this.#definitionFiles.has(file.displayPath));
|
|
446
|
+
const cached = cache.get(address);
|
|
447
|
+
if (cached?.complete === true &&
|
|
448
|
+
this.symbols.length + cached.symbols.length <= INDEX_RECORD_LIMIT &&
|
|
449
|
+
this.references.length + cached.references.length <= INDEX_RECORD_LIMIT &&
|
|
450
|
+
this.diagnostics.length + cached.diagnostics.length <= INDEX_DIAGNOSTIC_LIMIT) {
|
|
451
|
+
this.#currentFileShadowed = file.shadowedBy !== undefined;
|
|
452
|
+
for (const symbol of cached.symbols)
|
|
453
|
+
this.addSymbol(symbol);
|
|
454
|
+
for (const reference of cached.references)
|
|
455
|
+
this.addReference(reference);
|
|
456
|
+
this.addDiagnostics(cached.diagnostics);
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
const symbols = this.symbols.length;
|
|
460
|
+
const references = this.references.length;
|
|
461
|
+
const diagnostics = this.diagnostics.length;
|
|
462
|
+
this.indexFile(file);
|
|
463
|
+
// Incomplete files and aggregate ceilings retain the full builder's exact diagnostic
|
|
464
|
+
// ordering and coverage bookkeeping. They are never cached as complete segments.
|
|
465
|
+
if (this.#indexTableBlocked.has(file.displayPath) ||
|
|
466
|
+
this.#skippedSourcePaths.has(file.displayPath) ||
|
|
467
|
+
this.#indexNestingBlocked.has(file.displayPath) ||
|
|
468
|
+
this.#symbolLimitReported ||
|
|
469
|
+
this.#referenceLimitReported ||
|
|
470
|
+
this.#diagnosticsTruncated)
|
|
471
|
+
return;
|
|
472
|
+
cache.put(address, {
|
|
473
|
+
symbols: this.symbols.slice(symbols),
|
|
474
|
+
references: this.references.slice(references),
|
|
475
|
+
diagnostics: this.diagnostics.slice(diagnostics),
|
|
476
|
+
complete: true,
|
|
477
|
+
});
|
|
478
|
+
}
|
|
438
479
|
indexFile(file) {
|
|
439
480
|
this.#currentFileShadowed = file.shadowedBy !== undefined;
|
|
440
481
|
const diagnosticStart = this.diagnostics.length;
|
|
@@ -1163,6 +1204,7 @@ export class SymbolIndex {
|
|
|
1163
1204
|
let line = 1;
|
|
1164
1205
|
while (start <= text.length) {
|
|
1165
1206
|
if (line > INDEX_TABLE_RECORD_LIMIT) {
|
|
1207
|
+
this.#indexTableBlocked.add(file.displayPath);
|
|
1166
1208
|
this.#complete = false;
|
|
1167
1209
|
this.addDiagnostic({
|
|
1168
1210
|
code: 'INDEX_TABLE_RECORD_LIMIT',
|
|
@@ -1189,6 +1231,7 @@ export class SymbolIndex {
|
|
|
1189
1231
|
let match;
|
|
1190
1232
|
while ((match = matcher.exec(value)) !== null) {
|
|
1191
1233
|
if (fields.length >= INDEX_TABLE_FIELD_LIMIT) {
|
|
1234
|
+
this.#indexTableBlocked.add(file.displayPath);
|
|
1192
1235
|
this.#complete = false;
|
|
1193
1236
|
this.addDiagnostic({
|
|
1194
1237
|
code: 'INDEX_TABLE_FIELD_LIMIT',
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
import { JobService } from './job-service.js';
|
|
3
|
+
import { emptyServiceResult, ServiceError } from './result.js';
|
|
4
|
+
import { errorResult, strictOperationResultSchema, toolResult } from './operation-result.js';
|
|
5
|
+
import { requireOperationScope, resolveOperationWorkspaceId, } from './operation-context.js';
|
|
6
|
+
import { workspaceIdSchema } from '../schemas/common.js';
|
|
7
|
+
const jobIdSchema = z.string().regex(/^job_[a-f0-9]{64}$/u);
|
|
8
|
+
export const jobReferenceSchema = z
|
|
9
|
+
.object({ workspaceId: workspaceIdSchema, jobId: jobIdSchema })
|
|
10
|
+
.strict();
|
|
11
|
+
const jobStatusDataSchema = z
|
|
12
|
+
.object({
|
|
13
|
+
jobId: jobIdSchema,
|
|
14
|
+
toolName: z.string().max(256),
|
|
15
|
+
status: z.enum(['queued', 'running', 'reconciling', 'completed', 'cancelled', 'failed']),
|
|
16
|
+
revision: z.number().int().min(0),
|
|
17
|
+
createdAt: z.iso.datetime(),
|
|
18
|
+
updatedAt: z.iso.datetime(),
|
|
19
|
+
cancelRequested: z.boolean(),
|
|
20
|
+
resultReady: z.boolean(),
|
|
21
|
+
progress: z
|
|
22
|
+
.object({
|
|
23
|
+
completed: z.number().nonnegative(),
|
|
24
|
+
total: z.number().nonnegative().optional(),
|
|
25
|
+
message: z.string().max(4096),
|
|
26
|
+
})
|
|
27
|
+
.strict()
|
|
28
|
+
.optional(),
|
|
29
|
+
failure: z
|
|
30
|
+
.object({ code: z.string().max(256), message: z.string().max(4096) })
|
|
31
|
+
.strict()
|
|
32
|
+
.optional(),
|
|
33
|
+
})
|
|
34
|
+
.strict();
|
|
35
|
+
export const jobStatusOutputSchema = strictOperationResultSchema(jobStatusDataSchema);
|
|
36
|
+
export function isJobControlTool(name) {
|
|
37
|
+
return name === 'hoi4.job_inspect' || name === 'hoi4.job_cancel';
|
|
38
|
+
}
|
|
39
|
+
function statusResult(record) {
|
|
40
|
+
const result = emptyServiceResult(record.scope.workspaceId, {
|
|
41
|
+
jobId: record.id,
|
|
42
|
+
toolName: record.request.toolName,
|
|
43
|
+
status: record.status,
|
|
44
|
+
revision: record.revision,
|
|
45
|
+
createdAt: record.createdAt,
|
|
46
|
+
updatedAt: record.updatedAt,
|
|
47
|
+
cancelRequested: record.cancelRequested,
|
|
48
|
+
resultReady: record.result !== undefined,
|
|
49
|
+
...(record.progress === undefined ? {} : { progress: record.progress }),
|
|
50
|
+
...(record.failure === undefined ? {} : { failure: record.failure }),
|
|
51
|
+
});
|
|
52
|
+
result.code = record.status === 'failed' ? 'JOB_FAILED' : 'JOB_STATUS';
|
|
53
|
+
if (record.status === 'failed') {
|
|
54
|
+
result.status = 'error';
|
|
55
|
+
result.validation.passed = false;
|
|
56
|
+
result.blockers = [record.failure];
|
|
57
|
+
}
|
|
58
|
+
else if (record.status === 'cancelled') {
|
|
59
|
+
result.status = 'blocked';
|
|
60
|
+
result.code = 'JOB_CANCELLED';
|
|
61
|
+
result.validation.passed = false;
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
/** Bounded control traffic never acquires execution capacity or starts a worker. */
|
|
66
|
+
export class JobControlService {
|
|
67
|
+
engine;
|
|
68
|
+
jobsPromise;
|
|
69
|
+
constructor(engine, jobsPromise) {
|
|
70
|
+
this.engine = engine;
|
|
71
|
+
this.jobsPromise = jobsPromise;
|
|
72
|
+
}
|
|
73
|
+
async call(name, input, context, signal) {
|
|
74
|
+
const parsed = jobReferenceSchema.parse(input);
|
|
75
|
+
let workspaceId = parsed.workspaceId;
|
|
76
|
+
try {
|
|
77
|
+
signal?.throwIfAborted();
|
|
78
|
+
workspaceId = await resolveOperationWorkspaceId(this.engine, context, parsed.workspaceId, signal);
|
|
79
|
+
this.jobsPromise ??= JobService.create(this.engine.resolver);
|
|
80
|
+
const jobs = await this.jobsPromise;
|
|
81
|
+
const record = await jobs.get(workspaceId, parsed.jobId, context.principal, signal);
|
|
82
|
+
if (name === 'hoi4.job_cancel') {
|
|
83
|
+
if (record.request.mutation)
|
|
84
|
+
requireOperationScope(context, 'hoi4:write');
|
|
85
|
+
return toolResult(statusResult(await jobs.cancel(workspaceId, record.id, context.principal, signal)));
|
|
86
|
+
}
|
|
87
|
+
if (record.status !== 'completed')
|
|
88
|
+
return toolResult(statusResult(record));
|
|
89
|
+
if (record.result === undefined)
|
|
90
|
+
throw new ServiceError('JOB_RESULT_MISSING', 'Completed job has no authenticated result');
|
|
91
|
+
return {
|
|
92
|
+
...record.result,
|
|
93
|
+
_meta: {
|
|
94
|
+
...z.record(z.string(), z.json()).parse(record.result._meta ?? {}),
|
|
95
|
+
'io.github.klimPaskov/hoi4-agent-tools/job': {
|
|
96
|
+
jobId: record.id,
|
|
97
|
+
status: record.status,
|
|
98
|
+
revision: record.revision,
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
if (signal?.aborted)
|
|
105
|
+
throw error;
|
|
106
|
+
return errorResult(error, workspaceId);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|