hoi4-agent-tools 3.0.9 → 3.2.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 +32 -1
- package/README.md +10 -2
- package/dist/bin/stdio.js +23 -12
- package/dist/hoi4_agent_tools/core/analysis-checkpoints.js +82 -0
- package/dist/hoi4_agent_tools/core/analysis-evidence.js +9 -0
- package/dist/hoi4_agent_tools/core/analysis-job-operations.js +38 -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/condition-evaluator.js +16 -0
- 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/impact-comparison.js +49 -0
- package/dist/hoi4_agent_tools/core/impact-graph.js +301 -0
- package/dist/hoi4_agent_tools/core/impact-semantic.js +256 -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 +202 -0
- package/dist/hoi4_agent_tools/core/job-worker.js +64 -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 +387 -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/source-overlay.js +125 -0
- 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/decision/ai.js +104 -0
- package/dist/hoi4_agent_tools/decision/comparison.js +106 -0
- package/dist/hoi4_agent_tools/decision/cost.js +177 -0
- package/dist/hoi4_agent_tools/decision/evaluation.js +256 -0
- package/dist/hoi4_agent_tools/decision/lifecycle.js +114 -0
- package/dist/hoi4_agent_tools/decision/service.js +172 -0
- package/dist/hoi4_agent_tools/decision/source-inventory.js +182 -0
- 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/impact/scenario-suites.js +74 -0
- package/dist/hoi4_agent_tools/impact/service.js +127 -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 +18 -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 +45 -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 +16 -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/analysis.js +65 -0
- 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/analysis.js +228 -0
- 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 +3 -0
- package/docs/analysis.md +100 -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 +86 -0
- package/docs/technology.md +6 -2
- package/package.json +15 -6
- 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/decision-inspect-input.schema.json +290 -0
- 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/impact-inspect-input.schema.json +203 -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 +3 -3
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { CallToolResultSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import { jobTaskTiming } from '../../core/job-store.js';
|
|
3
|
+
import { OperationTaskService, operationTaskCallSchema, operationTaskId, operationTaskMessage, operationTaskStatus, } from '../../core/operation-tasks.js';
|
|
4
|
+
import { ServiceError } from '../../core/result.js';
|
|
5
|
+
import { currentTaskRequestSignal } from './task-request-context.js';
|
|
6
|
+
function legacyTask(record) {
|
|
7
|
+
if (record.request.protocolTask === undefined)
|
|
8
|
+
throw new ServiceError('TASK_NOT_FOUND', 'The requested protocol task does not exist');
|
|
9
|
+
const message = operationTaskMessage(record);
|
|
10
|
+
const timing = jobTaskTiming(record);
|
|
11
|
+
return {
|
|
12
|
+
taskId: operationTaskId(record),
|
|
13
|
+
status: operationTaskStatus(record),
|
|
14
|
+
createdAt: record.createdAt,
|
|
15
|
+
lastUpdatedAt: timing.lastUpdatedAt,
|
|
16
|
+
ttl: timing.ttl,
|
|
17
|
+
pollInterval: record.request.protocolTask.pollInterval,
|
|
18
|
+
...(message === undefined ? {} : { statusMessage: message }),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/** 2025 MCP task wire projection; execution and authorization live in the shared core. */
|
|
22
|
+
export class PersistentJobTaskStore {
|
|
23
|
+
context;
|
|
24
|
+
tasks;
|
|
25
|
+
constructor(engine, context) {
|
|
26
|
+
this.context = context;
|
|
27
|
+
this.tasks = new OperationTaskService(engine);
|
|
28
|
+
}
|
|
29
|
+
async createTask(taskParams, requestId, request, sessionId) {
|
|
30
|
+
if (request.method !== 'tools/call')
|
|
31
|
+
throw new ServiceError('TASK_METHOD_UNSUPPORTED', 'Only tool calls support background tasks');
|
|
32
|
+
const call = operationTaskCallSchema.parse({
|
|
33
|
+
name: request.params?.name,
|
|
34
|
+
arguments: request.params?.arguments,
|
|
35
|
+
});
|
|
36
|
+
const signal = currentTaskRequestSignal();
|
|
37
|
+
return legacyTask(await this.tasks.submit(call, this.context(), {
|
|
38
|
+
background: request.params?.task !== undefined,
|
|
39
|
+
requestId,
|
|
40
|
+
...(sessionId === undefined ? {} : { sessionId }),
|
|
41
|
+
...(taskParams.ttl === undefined ? {} : { ttlMs: taskParams.ttl }),
|
|
42
|
+
...(signal === undefined ? {} : { signal }),
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
async getTask(value, _sessionId) {
|
|
46
|
+
const record = await this.tasks.get(value, this.context());
|
|
47
|
+
return record === null ? null : legacyTask(record);
|
|
48
|
+
}
|
|
49
|
+
async storeTaskResult(value, status, result, _sessionId) {
|
|
50
|
+
await this.tasks.assertPublishedResult(value, status, result, this.context());
|
|
51
|
+
}
|
|
52
|
+
async getTaskResult(value, _sessionId) {
|
|
53
|
+
return CallToolResultSchema.parse(await this.tasks.result(value, this.context()));
|
|
54
|
+
}
|
|
55
|
+
async updateTaskStatus(value, status, _statusMessage, _sessionId) {
|
|
56
|
+
if (status !== 'cancelled')
|
|
57
|
+
throw new ServiceError('TASK_STATUS_IMMUTABLE', 'Protocol task state is controlled by the authenticated job executor');
|
|
58
|
+
await this.tasks.cancel(value, this.context());
|
|
59
|
+
}
|
|
60
|
+
async listTasks(cursor, _sessionId) {
|
|
61
|
+
const page = await this.tasks.list(this.context(), cursor);
|
|
62
|
+
return {
|
|
63
|
+
tasks: page.records.map(legacyTask),
|
|
64
|
+
...(page.nextCursor === undefined ? {} : { nextCursor: page.nextCursor }),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { CallToolResultSchema, CreateTaskResultSchema, GetTaskResultSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
/** SDK-v1-only registration boundary for the 2025 task protocol. */
|
|
3
|
+
export function registerLegacyTaskTools(server, definitions) {
|
|
4
|
+
for (const { name, ...configuration } of definitions) {
|
|
5
|
+
const handler = {
|
|
6
|
+
createTask: async (input, extra) => {
|
|
7
|
+
configuration.inputSchema.parse(input);
|
|
8
|
+
return CreateTaskResultSchema.parse({
|
|
9
|
+
task: await extra.taskStore.createTask({
|
|
10
|
+
ttl: extra.taskRequestedTtl ?? null,
|
|
11
|
+
pollInterval: 250,
|
|
12
|
+
}),
|
|
13
|
+
});
|
|
14
|
+
},
|
|
15
|
+
getTask: async (_input, extra) => GetTaskResultSchema.parse(await extra.taskStore.getTask(extra.taskId)),
|
|
16
|
+
getTaskResult: async (_input, extra) => CallToolResultSchema.parse(await extra.taskStore.getTaskResult(extra.taskId)),
|
|
17
|
+
};
|
|
18
|
+
server.experimental.tasks.registerToolTask(name, { ...configuration, execution: { taskSupport: 'optional' } }, handler);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
2
|
+
import { CallToolResultSchema } from '@modelcontextprotocol/core';
|
|
3
|
+
import { ProtocolError, ProtocolErrorCode } from '@modelcontextprotocol/server';
|
|
4
|
+
import { z } from 'zod/v4';
|
|
5
|
+
import { jobTaskTiming } from '../../core/job-store.js';
|
|
6
|
+
import { isMutationTask, operationTaskCallSchema, operationTaskId, operationTaskMessage, operationTaskStatus, } from '../../core/operation-tasks.js';
|
|
7
|
+
import { ServiceError } from '../../core/result.js';
|
|
8
|
+
import { taskToolCatalog } from './task-tool-catalog.js';
|
|
9
|
+
/** Stable 2026-07-28 Tasks extension, not the deprecated SDK TaskSchema. */
|
|
10
|
+
export const TASKS_EXTENSION = 'io.modelcontextprotocol/tasks';
|
|
11
|
+
const capabilitySchema = z
|
|
12
|
+
.object({
|
|
13
|
+
extensions: z.object({ [TASKS_EXTENSION]: z.object({}).loose() }).loose(),
|
|
14
|
+
})
|
|
15
|
+
.loose();
|
|
16
|
+
const taskReferenceSchema = z.object({ taskId: z.string().min(1).max(256) }).strict();
|
|
17
|
+
const taskUpdateSchema = taskReferenceSchema
|
|
18
|
+
.extend({
|
|
19
|
+
inputResponses: z
|
|
20
|
+
.record(z.string().max(1024), z.record(z.string(), z.json()))
|
|
21
|
+
.refine((value) => Object.keys(value).length <= 1024, 'Too many input responses'),
|
|
22
|
+
})
|
|
23
|
+
.strict();
|
|
24
|
+
const taskFields = {
|
|
25
|
+
taskId: z.string().min(1).max(256),
|
|
26
|
+
status: z.enum(['working', 'input_required', 'completed', 'failed', 'cancelled']),
|
|
27
|
+
statusMessage: z.string().max(8192).optional(),
|
|
28
|
+
createdAt: z.iso.datetime(),
|
|
29
|
+
lastUpdatedAt: z.iso.datetime(),
|
|
30
|
+
ttlMs: z.number().int().nonnegative().nullable(),
|
|
31
|
+
pollIntervalMs: z.number().int().nonnegative().optional(),
|
|
32
|
+
};
|
|
33
|
+
export const modernCreateTaskResultSchema = z
|
|
34
|
+
.object({
|
|
35
|
+
...taskFields,
|
|
36
|
+
resultType: z.literal('task'),
|
|
37
|
+
})
|
|
38
|
+
.strict();
|
|
39
|
+
export const modernGetTaskResultSchema = z.discriminatedUnion('status', [
|
|
40
|
+
z
|
|
41
|
+
.object({ ...taskFields, resultType: z.literal('complete'), status: z.literal('working') })
|
|
42
|
+
.strict(),
|
|
43
|
+
z
|
|
44
|
+
.object({
|
|
45
|
+
...taskFields,
|
|
46
|
+
resultType: z.literal('complete'),
|
|
47
|
+
status: z.literal('completed'),
|
|
48
|
+
result: z.record(z.string(), z.json()),
|
|
49
|
+
})
|
|
50
|
+
.strict(),
|
|
51
|
+
z
|
|
52
|
+
.object({ ...taskFields, resultType: z.literal('complete'), status: z.literal('cancelled') })
|
|
53
|
+
.strict(),
|
|
54
|
+
]);
|
|
55
|
+
export function hasModernTaskCapability(capabilities) {
|
|
56
|
+
return capabilitySchema.safeParse(capabilities).success;
|
|
57
|
+
}
|
|
58
|
+
function requireCapability(capabilities) {
|
|
59
|
+
if (!hasModernTaskCapability(capabilities))
|
|
60
|
+
throw new ProtocolError(ProtocolErrorCode.MissingRequiredClientCapability, 'Missing required client capability', {
|
|
61
|
+
requiredCapabilities: { extensions: { [TASKS_EXTENSION]: {} } },
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function taskFieldsFor(record, taskId) {
|
|
65
|
+
if (record.request.protocolTask === undefined)
|
|
66
|
+
throw new ProtocolError(ProtocolErrorCode.InvalidParams, 'Task not found');
|
|
67
|
+
const message = operationTaskMessage(record);
|
|
68
|
+
const timing = jobTaskTiming(record);
|
|
69
|
+
return {
|
|
70
|
+
taskId,
|
|
71
|
+
// Stored job failures are ordinary tool errors, not JSON-RPC failures.
|
|
72
|
+
status: record.status === 'failed' ? 'completed' : operationTaskStatus(record),
|
|
73
|
+
createdAt: record.createdAt,
|
|
74
|
+
lastUpdatedAt: timing.lastUpdatedAt,
|
|
75
|
+
ttlMs: timing.ttl,
|
|
76
|
+
pollIntervalMs: record.request.protocolTask.pollInterval,
|
|
77
|
+
...(message === undefined ? {} : { statusMessage: message }),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/** Projects the same authenticated job result onto the modern result vocabulary. */
|
|
81
|
+
function modernToolResult(record, result) {
|
|
82
|
+
const parsed = CallToolResultSchema.parse(result);
|
|
83
|
+
const definition = taskToolCatalog.find(({ name }) => name === record.request.toolName);
|
|
84
|
+
if (definition === undefined)
|
|
85
|
+
throw new ServiceError('TASK_TOOL_UNSUPPORTED', 'The task tool is not registered');
|
|
86
|
+
if (!parsed.isError || parsed.structuredContent !== undefined)
|
|
87
|
+
definition.outputSchema.parse(parsed.structuredContent);
|
|
88
|
+
return { ...parsed, resultType: 'complete', ttlMs: 0, cacheScope: 'private' };
|
|
89
|
+
}
|
|
90
|
+
/** Per-request negotiated wire adapter. It never accepts ownership or grants from client metadata. */
|
|
91
|
+
export class ModernTaskAdapter {
|
|
92
|
+
tasks;
|
|
93
|
+
constructor(tasks) {
|
|
94
|
+
this.tasks = tasks;
|
|
95
|
+
}
|
|
96
|
+
async call(input, capabilities, context, options) {
|
|
97
|
+
const parsed = operationTaskCallSchema.safeParse(input);
|
|
98
|
+
if (!parsed.success)
|
|
99
|
+
throw new ProtocolError(ProtocolErrorCode.InvalidParams, 'Invalid tool call');
|
|
100
|
+
// Capability advertisement is not a request to background a rewrite without a retry key.
|
|
101
|
+
const background = hasModernTaskCapability(capabilities) &&
|
|
102
|
+
(!isMutationTask(parsed.data.name) || typeof parsed.data.arguments.requestKey === 'string');
|
|
103
|
+
const record = await this.tasks.submit(parsed.data, context, { ...options, background });
|
|
104
|
+
const taskId = this.tasks.authenticatedTaskId(record);
|
|
105
|
+
if (background)
|
|
106
|
+
return modernCreateTaskResultSchema.parse({
|
|
107
|
+
resultType: 'task',
|
|
108
|
+
...taskFieldsFor(record, taskId),
|
|
109
|
+
});
|
|
110
|
+
let latest = record;
|
|
111
|
+
while (operationTaskStatus(latest) === 'working') {
|
|
112
|
+
options.signal?.throwIfAborted();
|
|
113
|
+
await delay(100, undefined, { signal: options.signal });
|
|
114
|
+
latest = await this.known(taskId, context);
|
|
115
|
+
}
|
|
116
|
+
return modernToolResult(latest, await this.tasks.result(operationTaskId(record), context));
|
|
117
|
+
}
|
|
118
|
+
async handle(method, params, capabilities, context, signal) {
|
|
119
|
+
if (!['tasks/get', 'tasks/update', 'tasks/cancel'].includes(method))
|
|
120
|
+
throw new ProtocolError(ProtocolErrorCode.MethodNotFound, 'Method not found');
|
|
121
|
+
requireCapability(capabilities);
|
|
122
|
+
signal?.throwIfAborted();
|
|
123
|
+
const parsed = (method === 'tasks/update' ? taskUpdateSchema : taskReferenceSchema).safeParse(params);
|
|
124
|
+
if (!parsed.success)
|
|
125
|
+
throw new ProtocolError(ProtocolErrorCode.InvalidParams, 'Invalid task parameters');
|
|
126
|
+
try {
|
|
127
|
+
const record = await this.known(parsed.data.taskId, context, method === 'tasks/get');
|
|
128
|
+
signal?.throwIfAborted();
|
|
129
|
+
if (method === 'tasks/cancel') {
|
|
130
|
+
await this.tasks.cancel(operationTaskId(record), context);
|
|
131
|
+
return { resultType: 'complete' };
|
|
132
|
+
}
|
|
133
|
+
// These offline operations never solicit input during execution. Unknown/satisfied keys
|
|
134
|
+
// are acknowledged without changing state, as required by the extension.
|
|
135
|
+
if (method === 'tasks/update')
|
|
136
|
+
return { resultType: 'complete' };
|
|
137
|
+
const fields = taskFieldsFor(record, parsed.data.taskId);
|
|
138
|
+
return modernGetTaskResultSchema.parse({
|
|
139
|
+
...fields,
|
|
140
|
+
resultType: 'complete',
|
|
141
|
+
...(fields.status !== 'completed'
|
|
142
|
+
? {}
|
|
143
|
+
: {
|
|
144
|
+
result: modernToolResult(record, await this.tasks.result(operationTaskId(record), context)),
|
|
145
|
+
}),
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
if (error instanceof ProtocolError)
|
|
150
|
+
throw error;
|
|
151
|
+
if (error instanceof ServiceError && error.code === 'TASK_NOT_FOUND')
|
|
152
|
+
throw new ProtocolError(ProtocolErrorCode.InvalidParams, 'Task not found');
|
|
153
|
+
if (error instanceof ServiceError && error.code === 'AUTH_SCOPE_REQUIRED')
|
|
154
|
+
throw new ProtocolError(-32000, 'The operation requires write authorization', {
|
|
155
|
+
code: error.code,
|
|
156
|
+
});
|
|
157
|
+
if (signal?.aborted)
|
|
158
|
+
throw error;
|
|
159
|
+
throw new ProtocolError(ProtocolErrorCode.InternalError, 'The task operation could not be completed');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async known(value, context, resume = true) {
|
|
163
|
+
const record = await this.tasks.getAuthenticatedTask(value, context, { resume });
|
|
164
|
+
if (record === null)
|
|
165
|
+
throw new ProtocolError(ProtocolErrorCode.InvalidParams, 'Task not found');
|
|
166
|
+
return record;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -6,35 +6,39 @@ export const PROGRESS_HEARTBEAT_INTERVAL_MS = 10_000;
|
|
|
6
6
|
const reporters = new WeakMap();
|
|
7
7
|
const heartbeatOwners = new WeakSet();
|
|
8
8
|
export function progressReporter(extra) {
|
|
9
|
-
|
|
9
|
+
return reporterFor(extra, extra.signal, extra._meta?.progressToken, (notification) => extra.sendNotification(notification));
|
|
10
|
+
}
|
|
11
|
+
/** Use the SDK's request-related notification route, including HTTP response-stream association. */
|
|
12
|
+
export function modernProgressReporter(context) {
|
|
13
|
+
const request = context.mcpReq;
|
|
14
|
+
return reporterFor(request, request.signal, request._meta?.progressToken, (notification) => request.notify(notification));
|
|
15
|
+
}
|
|
16
|
+
function reporterFor(owner, signal, progressToken, notify) {
|
|
17
|
+
const existing = reporters.get(owner);
|
|
10
18
|
if (existing !== undefined)
|
|
11
19
|
return existing;
|
|
12
20
|
let latest = Number.NEGATIVE_INFINITY;
|
|
13
21
|
let latestTotal = 1;
|
|
14
22
|
let latestMessage = 'Waiting for server execution capacity';
|
|
15
23
|
const reporter = {
|
|
16
|
-
signal
|
|
24
|
+
signal,
|
|
17
25
|
async report(progress, total, message) {
|
|
18
|
-
|
|
26
|
+
signal.throwIfAborted();
|
|
19
27
|
latestTotal = total;
|
|
20
28
|
latestMessage = message;
|
|
21
29
|
const normalized = Math.max(latest, Math.min(progress, total));
|
|
22
|
-
const progressToken = extra._meta?.progressToken;
|
|
23
30
|
if (progressToken === undefined)
|
|
24
31
|
return;
|
|
25
32
|
if (normalized <= latest)
|
|
26
33
|
return;
|
|
27
34
|
latest = normalized;
|
|
28
|
-
await
|
|
29
|
-
.sendNotification({
|
|
35
|
+
await notify({
|
|
30
36
|
method: 'notifications/progress',
|
|
31
37
|
params: { progressToken, progress: normalized, total, message },
|
|
32
|
-
})
|
|
33
|
-
.catch(() => undefined);
|
|
38
|
+
}).catch(() => undefined);
|
|
34
39
|
},
|
|
35
40
|
async pulse(message = latestMessage) {
|
|
36
|
-
|
|
37
|
-
const progressToken = extra._meta?.progressToken;
|
|
41
|
+
signal.throwIfAborted();
|
|
38
42
|
if (progressToken === undefined)
|
|
39
43
|
return;
|
|
40
44
|
// Use the next representably larger value rather than repeating a value, which
|
|
@@ -42,8 +46,7 @@ export function progressReporter(extra) {
|
|
|
42
46
|
latest = Number.isFinite(latest)
|
|
43
47
|
? latest + Math.max(1, Math.abs(latest)) * Number.EPSILON * 2
|
|
44
48
|
: 0;
|
|
45
|
-
await
|
|
46
|
-
.sendNotification({
|
|
49
|
+
await notify({
|
|
47
50
|
method: 'notifications/progress',
|
|
48
51
|
params: {
|
|
49
52
|
progressToken,
|
|
@@ -51,11 +54,10 @@ export function progressReporter(extra) {
|
|
|
51
54
|
...(latest <= latestTotal ? { total: latestTotal } : {}),
|
|
52
55
|
message,
|
|
53
56
|
},
|
|
54
|
-
})
|
|
55
|
-
.catch(() => undefined);
|
|
57
|
+
}).catch(() => undefined);
|
|
56
58
|
},
|
|
57
59
|
};
|
|
58
|
-
reporters.set(
|
|
60
|
+
reporters.set(owner, reporter);
|
|
59
61
|
return reporter;
|
|
60
62
|
}
|
|
61
63
|
/**
|
|
@@ -1,4 +1,36 @@
|
|
|
1
|
+
import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
1
2
|
import { progressReporter, withProgressHeartbeat } from './progress.js';
|
|
3
|
+
import { withTaskRequestSignal } from './task-request-context.js';
|
|
4
|
+
// Durable task compatibility and cancellation are control traffic. They must
|
|
5
|
+
// remain responsive while every domain-execution slot is occupied.
|
|
6
|
+
const controlTools = new Set(['hoi4.job_inspect', 'hoi4.job_cancel']);
|
|
7
|
+
const backgroundTools = new Set([
|
|
8
|
+
'hoi4.impact_inspect',
|
|
9
|
+
'hoi4.decision_inspect',
|
|
10
|
+
'hoi4.event_inspect',
|
|
11
|
+
'hoi4.event_render',
|
|
12
|
+
'hoi4.event_compare',
|
|
13
|
+
'hoi4.tech_inspect',
|
|
14
|
+
'hoi4.tech_render',
|
|
15
|
+
'hoi4.tech_compare',
|
|
16
|
+
'hoi4.probability_inspect',
|
|
17
|
+
'hoi4.probability_evaluate',
|
|
18
|
+
'hoi4.probability_sweep',
|
|
19
|
+
'hoi4.probability_simulate',
|
|
20
|
+
'hoi4.probability_sequence',
|
|
21
|
+
'hoi4.probability_compare',
|
|
22
|
+
'hoi4.probability_render',
|
|
23
|
+
'hoi4.map_inspect',
|
|
24
|
+
'hoi4.map_render',
|
|
25
|
+
'hoi4.map_rewrite',
|
|
26
|
+
'hoi4.gui_inspect',
|
|
27
|
+
'hoi4.gui_render',
|
|
28
|
+
'hoi4.gui_rewrite',
|
|
29
|
+
'hoi4.focus_inspect',
|
|
30
|
+
'hoi4.focus_render',
|
|
31
|
+
'hoi4.focus_raster',
|
|
32
|
+
'hoi4.focus_rewrite',
|
|
33
|
+
]);
|
|
2
34
|
/** Wrap the public SDK handler registration so optional tools receive the same lifecycle. */
|
|
3
35
|
export function installRequestLifecycle(server, engine) {
|
|
4
36
|
const register = server.server.setRequestHandler.bind(server.server);
|
|
@@ -8,6 +40,19 @@ export function installRequestLifecycle(server, engine) {
|
|
|
8
40
|
if (request.method !== 'tools/call')
|
|
9
41
|
return handler(request, extra);
|
|
10
42
|
const progress = progressReporter(extra);
|
|
43
|
+
const taskCall = CallToolRequestSchema.safeParse(request);
|
|
44
|
+
if (taskCall.success && controlTools.has(taskCall.data.params.name))
|
|
45
|
+
return handler(request, extra);
|
|
46
|
+
if (taskCall.success && backgroundTools.has(taskCall.data.params.name)) {
|
|
47
|
+
return withProgressHeartbeat(async () => {
|
|
48
|
+
const result = await withTaskRequestSignal(extra.signal, () => handler(request, extra));
|
|
49
|
+
if (taskCall.data.params.task === undefined) {
|
|
50
|
+
await progress.report(2, 3, 'Retrieving persistent operation result');
|
|
51
|
+
await progress.report(3, 3, 'Persistent operation complete');
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}, progress);
|
|
55
|
+
}
|
|
11
56
|
return withProgressHeartbeat(() => engine.requests.run(owner, Buffer.byteLength(JSON.stringify(request)), extra.signal, () => engine.sharedRequests.run(extra.signal, async () => handler(request, extra))), progress);
|
|
12
57
|
});
|
|
13
58
|
};
|