tododdle-mcp 3.7.0 → 3.8.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/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +43 -7
- package/config/external-api-parity.json +9 -1
- package/dist/compatibility.d.ts +2 -2
- package/dist/compatibility.js +1 -1
- package/dist/config.d.ts +6 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +14 -4
- package/dist/config.js.map +1 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +337 -25
- package/dist/server.js.map +1 -1
- package/dist/upload-source.d.ts +22 -1
- package/dist/upload-source.d.ts.map +1 -1
- package/dist/upload-source.js +87 -36
- package/dist/upload-source.js.map +1 -1
- package/package.json +2 -2
- package/skills/tododdle-supervisor/SKILL.md +5 -3
- package/skills/tododdle-workflow/SKILL.md +2 -1
- package/skills/tododdle-workflow/references/files.md +5 -5
- package/skills/tododdle-workflow/references/project-work.md +1 -1
- package/skills/tododdle-workflow/references/releases.md +35 -0
- package/skills/tododdle-workflow/references/support-cases.md +2 -2
package/dist/upload-source.js
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
import { createWriteStream } from 'node:fs';
|
|
2
|
-
import {
|
|
3
|
-
import { lookup } from 'node:dns/promises';
|
|
2
|
+
import { lstat, mkdtemp, realpath, rm, rmdir } from 'node:fs/promises';
|
|
3
|
+
import { lookup as dnsLookup } from 'node:dns/promises';
|
|
4
|
+
import { request as httpsRequest } from 'node:https';
|
|
4
5
|
import { isIP } from 'node:net';
|
|
5
6
|
import { basename, dirname, extname, relative, resolve, sep } from 'node:path';
|
|
6
7
|
import { tmpdir } from 'node:os';
|
|
7
|
-
import {
|
|
8
|
+
import { Transform } from 'node:stream';
|
|
8
9
|
import { pipeline } from 'node:stream/promises';
|
|
9
10
|
const URL_TIMEOUT_MS = 60_000;
|
|
10
11
|
const MAX_REDIRECTS = 5;
|
|
11
|
-
export async function ensureManagedUploadRoot(root) {
|
|
12
|
-
await mkdir(root, { recursive: true, mode: 0o700 });
|
|
13
|
-
if (process.platform !== 'win32')
|
|
14
|
-
await chmod(root, 0o700);
|
|
15
|
-
return realpath(root);
|
|
16
|
-
}
|
|
17
12
|
const mimeTypes = {
|
|
18
13
|
'.csv': 'text/csv',
|
|
19
14
|
'.gif': 'image/gif',
|
|
@@ -47,16 +42,29 @@ function sanitizeFileName(value) {
|
|
|
47
42
|
async function prepareLocalSource(input, roots, managedRoot, maxBytes) {
|
|
48
43
|
if (!input.filePath)
|
|
49
44
|
throw new Error('filePath is required');
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
const
|
|
45
|
+
const requestedPath = resolve(input.filePath);
|
|
46
|
+
// Reject a symlink as the final path component before following it.
|
|
47
|
+
const requestedStats = await lstat(requestedPath);
|
|
48
|
+
if (!requestedStats.isFile())
|
|
49
|
+
throw new Error('Local upload source must be a regular file');
|
|
50
|
+
const resolvedFile = await realpath(requestedPath);
|
|
51
|
+
const resolvedRoots = await Promise.all(roots.map((root) => realpath(resolve(root)).catch(() => null)));
|
|
52
|
+
const resolvedManagedRoot = managedRoot
|
|
53
|
+
? await realpath(resolve(managedRoot)).catch(() => null)
|
|
54
|
+
: null;
|
|
53
55
|
const isManagedFile = Boolean(resolvedManagedRoot && isInsideRoot(resolvedFile, resolvedManagedRoot));
|
|
54
|
-
if (!isManagedFile &&
|
|
55
|
-
|
|
56
|
+
if (!isManagedFile &&
|
|
57
|
+
!resolvedRoots.some((root) => root !== null && isInsideRoot(resolvedFile, root))) {
|
|
58
|
+
throw new Error('Local file must be in the system temporary directory or an approved upload root');
|
|
56
59
|
}
|
|
57
60
|
const stats = await lstat(resolvedFile);
|
|
58
61
|
if (!stats.isFile())
|
|
59
62
|
throw new Error('Local upload source must be a regular file');
|
|
63
|
+
if (process.platform !== 'win32' && typeof process.getuid === 'function') {
|
|
64
|
+
if (stats.uid !== process.getuid()) {
|
|
65
|
+
throw new Error('Local upload source must be owned by the current user');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
60
68
|
if (stats.size <= 0 || stats.size > maxBytes)
|
|
61
69
|
throw new Error('Local file size is not allowed');
|
|
62
70
|
const fileName = sanitizeFileName(input.fileName || basename(resolvedFile));
|
|
@@ -97,29 +105,68 @@ function isPrivateAddress(address) {
|
|
|
97
105
|
(a === 192 && b === 168) ||
|
|
98
106
|
(a === 100 && b >= 64 && b <= 127));
|
|
99
107
|
}
|
|
100
|
-
|
|
108
|
+
const defaultResolver = (hostname) => dnsLookup(hostname, { all: true });
|
|
109
|
+
function assertPublicAddresses(addresses) {
|
|
110
|
+
if (addresses.length === 0 || addresses.some(({ address }) => isPrivateAddress(address))) {
|
|
111
|
+
throw new Error('Upload source URL resolves to a private or reserved address');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Builds a `net` lookup function that validates every resolved address at connect time.
|
|
116
|
+
* The socket connects only to addresses this function returns, so a DNS answer that changes
|
|
117
|
+
* between validation and connection (DNS rebinding) cannot reach a private address.
|
|
118
|
+
*/
|
|
119
|
+
export function createValidatingLookup(resolver = defaultResolver) {
|
|
120
|
+
return (hostname, options, callback) => {
|
|
121
|
+
resolver(hostname).then((addresses) => {
|
|
122
|
+
try {
|
|
123
|
+
assertPublicAddresses(addresses);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
callback(error);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (options?.all)
|
|
130
|
+
callback(null, addresses);
|
|
131
|
+
else
|
|
132
|
+
callback(null, addresses[0].address, addresses[0].family);
|
|
133
|
+
}, (error) => callback(error));
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function validateUrlShape(url) {
|
|
101
137
|
if (url.protocol !== 'https:')
|
|
102
138
|
throw new Error('Only HTTPS upload sources are allowed');
|
|
103
139
|
if (url.username || url.password)
|
|
104
140
|
throw new Error('Upload source URLs cannot contain credentials');
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (addresses.length === 0 || addresses.some(({ address }) => isPrivateAddress(address))) {
|
|
109
|
-
throw new Error('Upload source URL resolves to a private or reserved address');
|
|
110
|
-
}
|
|
141
|
+
const literal = url.hostname.replace(/^\[|\]$/g, '');
|
|
142
|
+
if (isIP(literal))
|
|
143
|
+
assertPublicAddresses([{ address: literal }]);
|
|
111
144
|
}
|
|
112
|
-
|
|
145
|
+
function requestPinned(url, resolver) {
|
|
146
|
+
return new Promise((resolvePromise, reject) => {
|
|
147
|
+
const request = httpsRequest(url, {
|
|
148
|
+
method: 'GET',
|
|
149
|
+
lookup: createValidatingLookup(resolver),
|
|
150
|
+
timeout: URL_TIMEOUT_MS,
|
|
151
|
+
signal: AbortSignal.timeout(URL_TIMEOUT_MS),
|
|
152
|
+
headers: { 'user-agent': 'tododdle-mcp' },
|
|
153
|
+
}, resolvePromise);
|
|
154
|
+
request.on('timeout', () => request.destroy(new Error('Upload source request timed out')));
|
|
155
|
+
request.on('error', reject);
|
|
156
|
+
request.end();
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
export async function fetchWithSafeRedirects(initialUrl, resolver = defaultResolver) {
|
|
113
160
|
let url = new URL(initialUrl);
|
|
114
161
|
for (let redirects = 0; redirects <= MAX_REDIRECTS; redirects += 1) {
|
|
115
|
-
|
|
116
|
-
const response = await
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const location = response.headers.
|
|
162
|
+
validateUrlShape(url);
|
|
163
|
+
const response = await requestPinned(url, resolver);
|
|
164
|
+
const status = response.statusCode ?? 0;
|
|
165
|
+
if (![301, 302, 303, 307, 308].includes(status)) {
|
|
166
|
+
return { status, headers: response.headers, stream: response, url: url.href };
|
|
167
|
+
}
|
|
168
|
+
response.resume();
|
|
169
|
+
const location = response.headers.location;
|
|
123
170
|
if (!location)
|
|
124
171
|
throw new Error('Upload source redirect is missing a location');
|
|
125
172
|
url = new URL(location, url);
|
|
@@ -130,13 +177,17 @@ async function prepareUrlSource(input, maxBytes) {
|
|
|
130
177
|
if (!input.sourceUrl)
|
|
131
178
|
throw new Error('sourceUrl is required');
|
|
132
179
|
const response = await fetchWithSafeRedirects(input.sourceUrl);
|
|
133
|
-
if (
|
|
180
|
+
if (response.status < 200 || response.status > 299) {
|
|
181
|
+
response.stream.resume();
|
|
134
182
|
throw new Error(`Upload source returned ${response.status}`);
|
|
135
|
-
|
|
136
|
-
|
|
183
|
+
}
|
|
184
|
+
const declaredLength = Number(response.headers['content-length'] || 0);
|
|
185
|
+
if (declaredLength > maxBytes) {
|
|
186
|
+
response.stream.destroy();
|
|
137
187
|
throw new Error('Remote file exceeds the maximum upload size');
|
|
188
|
+
}
|
|
138
189
|
const directory = await mkdtemp(resolve(tmpdir(), 'tododdle-upload-'));
|
|
139
|
-
const sourceName = basename(new URL(response.url
|
|
190
|
+
const sourceName = basename(new URL(response.url).pathname) || 'download';
|
|
140
191
|
const fileName = sanitizeFileName(input.fileName || sourceName);
|
|
141
192
|
const stagedPath = resolve(directory, fileName);
|
|
142
193
|
let bytes = 0;
|
|
@@ -150,7 +201,7 @@ async function prepareUrlSource(input, maxBytes) {
|
|
|
150
201
|
},
|
|
151
202
|
});
|
|
152
203
|
try {
|
|
153
|
-
await pipeline(
|
|
204
|
+
await pipeline(response.stream, limiter, createWriteStream(stagedPath, { flags: 'wx', mode: 0o600 }));
|
|
154
205
|
if (bytes <= 0)
|
|
155
206
|
throw new Error('Remote file is empty');
|
|
156
207
|
return {
|
|
@@ -158,7 +209,7 @@ async function prepareUrlSource(input, maxBytes) {
|
|
|
158
209
|
fileName,
|
|
159
210
|
fileSize: bytes,
|
|
160
211
|
contentType: input.contentType ||
|
|
161
|
-
response.headers
|
|
212
|
+
response.headers['content-type']?.split(';')[0] ||
|
|
162
213
|
inferContentType(fileName),
|
|
163
214
|
cleanup: async () => rm(directory, { recursive: true, force: true }),
|
|
164
215
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upload-source.js","sourceRoot":"","sources":["../src/upload-source.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"upload-source.js","sourceRoot":"","sources":["../src/upload-source.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tododdle-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
4
|
"description": "Agent-first task, project context, and time tracking for ToDoddle",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"provenance": true
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
55
|
+
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
56
56
|
"zod": "^3.24.1"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: tododdle-supervisor
|
|
3
|
-
description: Use current ToDoddle supervision assessments to guide coding work at meaningful decision boundaries. Apply when supervised execution is requested and a compatible decision
|
|
3
|
+
description: Use current ToDoddle supervision assessments to guide coding work at meaningful decision boundaries. Apply when supervised execution is requested and a compatible decision or supervision provider is available through ToDoddle; use tododdle-workflow alone when it is not.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# ToDoddle Supervisor
|
|
@@ -9,9 +9,11 @@ Read [tododdle-workflow](../tododdle-workflow/SKILL.md) first. Follow its author
|
|
|
9
9
|
|
|
10
10
|
## Check Availability
|
|
11
11
|
|
|
12
|
-
Use only the `tododdle` MCP. Never call
|
|
12
|
+
Use only the `tododdle` MCP. Never call an external decision or supervision provider directly, and never handle its credentials.
|
|
13
13
|
|
|
14
|
-
Treat supervision as available for a Ticket only when `get_supervisor_state` succeeds and returns a current assessment from the project’s configured decision evaluator. A current assessment must postdate the Ticket state and meaningful event it evaluates. Accept
|
|
14
|
+
Treat supervision as available for a Ticket only when `get_supervisor_state` succeeds and returns a current assessment from the project’s configured decision evaluator. A current assessment must postdate the Ticket state and meaningful event it evaluates. Accept only evaluator types and assessment schemas that project Context explicitly approves. Apply the same checks to every provider. Do not infer availability from a provider name, evaluator type, or product announcement.
|
|
15
|
+
|
|
16
|
+
Compatible systems can include decision APIs, supervision services, model reviewers, and deterministic rules. Provider adapters map their output to ToDoddle assessments. A System One adapter is one example, not a required wire format. Jev and OpenAI Decisions are candidate integrations; neither name proves that an adapter is configured or supported.
|
|
15
17
|
|
|
16
18
|
Treat supervision as unavailable when the tool is missing, the call fails, no approved assessment exists, or the latest assessment is stale, incomplete, or conflicting. Do not call `record_assessment` to create your own guidance. Do not poll. Continue with `tododdle-workflow` when supervision is optional. If project rules require a supervised decision, preserve the current state and add an escalation instead of guessing.
|
|
17
19
|
|
|
@@ -56,7 +56,7 @@ For detailed project structure and execution rules, read [project-work.md](refer
|
|
|
56
56
|
1. Run appropriate verification.
|
|
57
57
|
2. Add one typed `HANDOFF` comment with `## Outcome`, `## Verification`, and `## Remaining` when useful. Include stable links and immutable commit, release, document, or artifact IDs. Never store expiring URLs.
|
|
58
58
|
3. Call `finish_agent_run` once with a short outcome and durable evidence. Use `SUCCEEDED`, `FAILED`, or `CANCELLED`. Reuse its idempotency key if delivery is uncertain.
|
|
59
|
-
4. Compare the ticket creator with the authenticated Agent Connection user, not the assignee. If they match, set `COMPLETE` after the result is achieved and verified. If they differ, move the ticket to its existing `REVIEW` status and hand off to the creator or an explicitly named human reviewer. Keep it in `REVIEW` until that person records acceptance, then set `COMPLETE`. Do not add a ticket status.
|
|
59
|
+
4. Compare the ticket creator with the authenticated Agent Connection user, not the assignee. If they match, set `COMPLETE` after the result is achieved and verified. If they differ, move the ticket to its existing `REVIEW` status and hand off to the creator or an explicitly named human reviewer. Keep it in `REVIEW` until that person records acceptance, then set `COMPLETE`. Do not add a ticket status. The server enforces this: an agent that sets `COMPLETE` on a ticket another user created gets `403 permission_denied`. Set `REVIEW` and call `create_review_request`. When a run is active, pass `runId` and `projectId` on `move_ticket`, `archive_ticket`, attribute tools, and time tools.
|
|
60
60
|
5. Agent Run success and supervision assessment are evidence, not human acceptance. A run result never changes ticket status. A review request is separate from ticket status; archive is separate cleanup.
|
|
61
61
|
6. Use `release_ticket` only when stopping without a terminal result. It records the run as cancelled.
|
|
62
62
|
7. Keep incomplete work active and state what remains.
|
|
@@ -69,6 +69,7 @@ When resuming, read the agent inbox, handle the reply, then reload the linked it
|
|
|
69
69
|
- **Time tracking:** Read [time-tracking.md](references/time-tracking.md).
|
|
70
70
|
- **Scheduled checks:** Read [scheduled-workflows.md](references/scheduled-workflows.md).
|
|
71
71
|
- **Support cases:** Read [support-cases.md](references/support-cases.md).
|
|
72
|
+
- **Releases or Testbench rounds:** Read [releases.md](references/releases.md).
|
|
72
73
|
|
|
73
74
|
## Missing Access
|
|
74
75
|
|
|
@@ -5,13 +5,13 @@ Read this file only when uploading or reviewing a ToDoddle file.
|
|
|
5
5
|
## Upload a Local File
|
|
6
6
|
|
|
7
7
|
- Use `upload_project_document` or `attach_file_to_ticket` for approved files.
|
|
8
|
-
- For a clipboard image, create a private directory with `mktemp -d`
|
|
9
|
-
-
|
|
10
|
-
- After confirmed success, the local MCP deletes the staged file and its empty
|
|
8
|
+
- For a clipboard image, create a private directory with `mktemp -d` in the system temporary directory. Copy the image into it and keep a suitable extension. Do not alter the original.
|
|
9
|
+
- Files must be regular files (no symlinks) in the system temporary directory or an optional approved root, and owned by the current user. If a source is elsewhere, copy it into a private directory in the system temporary directory. Never move the original.
|
|
10
|
+
- After confirmed success, the local MCP deletes the staged file and its empty parent directory.
|
|
11
11
|
- If upload fails or is uncertain, keep the staged file for retry and report its path.
|
|
12
|
-
- Never delete the user’s original, a pre-existing file, or a file outside
|
|
12
|
+
- Never delete the user’s original, a pre-existing file, or a file outside the system temporary directory that the agent did not create.
|
|
13
13
|
|
|
14
|
-
`TODODDLE_UPLOAD_ROOTS` is optional. Use it only for Docker, sandboxes, or approved permanent directories.
|
|
14
|
+
`TODODDLE_UPLOAD_ROOTS` is optional. Use it only for Docker, sandboxes, or approved permanent directories. The system temporary directory is always allowed.
|
|
15
15
|
|
|
16
16
|
## Upload Through Hosted MCP
|
|
17
17
|
|
|
@@ -32,7 +32,7 @@ Read this file only when creating project structure or when execution needs thes
|
|
|
32
32
|
|
|
33
33
|
- Use `list_review_requests` with one project and narrow filters. Read one request with `get_review_request` before changing it.
|
|
34
34
|
- A formal review request is optional. It does not change ticket status or block work.
|
|
35
|
-
- Compare the ticket creator with the authenticated Agent Connection user, not the assignee. If they match, mark the ticket `COMPLETE` after the result is achieved and verified. If they differ, move it to its existing `REVIEW` status and hand it off to the creator or a named human reviewer. Keep it in `REVIEW` until that person records acceptance, then mark it `COMPLETE`. Use a formal review request when useful; a clear creator or reviewer response can also record acceptance. Do not infer acceptance from an Agent Run or supervisor result. Do not add a ticket status.
|
|
35
|
+
- Compare the ticket creator with the authenticated Agent Connection user, not the assignee. If they match, mark the ticket `COMPLETE` after the result is achieved and verified. If they differ, move it to its existing `REVIEW` status and hand it off to the creator or a named human reviewer. Keep it in `REVIEW` until that person records acceptance, then mark it `COMPLETE`. Use a formal review request when useful; a clear creator or reviewer response can also record acceptance. Do not infer acceptance from an Agent Run or supervisor result. Do not add a ticket status. The server enforces this: setting `COMPLETE` on a ticket another user created returns `403 permission_denied`, so set `REVIEW` and call `create_review_request`.
|
|
36
36
|
- Use `list_project_members` before selecting reviewers. Do not guess user IDs.
|
|
37
37
|
- Create a review only for an accessible ticket, document, board, or Context artifact. Supply an idempotency key for every create, review comment, and checklist addition. Reuse the same key only when retrying that same operation.
|
|
38
38
|
- Only a named reviewer can use `respond_to_review_request`. Use that reviewer’s current `updatedAt` value as `expectedUpdatedAt`.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Releases and Testbench Rounds
|
|
2
|
+
|
|
3
|
+
Releases group tickets for one shipped version. Use the release tools only when the selected ticket or the user asks for release work.
|
|
4
|
+
|
|
5
|
+
## Access
|
|
6
|
+
|
|
7
|
+
- Release reads need `releases:read`. Release writes need `releases:write`.
|
|
8
|
+
- Testbench reads need `testbench:read`. Testbench writes need `testbench:write`.
|
|
9
|
+
- Every call needs a project grant. Writes, and all Testbench calls, need project owner or admin access.
|
|
10
|
+
- `409 BETA_DISABLED` means Testbench is off for the project. Report it. Do not try to turn it on.
|
|
11
|
+
|
|
12
|
+
## Read
|
|
13
|
+
|
|
14
|
+
- Use `list_releases` with `status`, `search`, or `includeArchived` to find a release. Follow `hasMore` with the next `page` only when you need it.
|
|
15
|
+
- Use `get_release` for one release. It returns up to 50 ticket summaries, the total `ticketCount`, and linked Testbench rounds. `tickets` and `ticketCount` are `null` without `tasks:read`, and `testbenchRounds` is `null` without `testbench:read` and Testbench operator access. Treat `null` as "not visible to this connection", not as "none".
|
|
16
|
+
- Use `list_testbench_rounds` to find a round and its current `revision`.
|
|
17
|
+
|
|
18
|
+
## Change
|
|
19
|
+
|
|
20
|
+
- `create_release` creates a `PENDING` release. Pass a stable `idempotencyKey` so a retry does not create a second release.
|
|
21
|
+
- `update_release` edits version, title, build, environment, build link, and text metadata. Pass `expectedRevision` from the latest read. Reload after a `409 CONFLICT`. Archived releases cannot be edited.
|
|
22
|
+
- Link a ticket with `create_ticket` or `update_ticket` and `releaseId`. Set `releaseId` to `null` to remove it. Filter tickets with `list_tickets` and `releaseId`, or `none`.
|
|
23
|
+
- `set_round_release` links a `DRAFT` round to a release, or removes the link with `releaseId: null`. Pass the round `revision` as `expectedRevision`. It never publishes the round or sends email.
|
|
24
|
+
|
|
25
|
+
## Status, Archive, and Restore Need Approval
|
|
26
|
+
|
|
27
|
+
- Use `request_release_status_change` to publish, abandon, return to pending, archive, or restore a release. Give a short reason and the latest `expectedRevision`.
|
|
28
|
+
- The tool does not change the release. A person approves or rejects the request in AIr Traffic Control. Nothing changes until then.
|
|
29
|
+
- The result shows `result: PENDING_APPROVAL` and `changed: false`. Do not report the release as published, abandoned, archived, or restored. Share the `uiUrl` when a person needs to decide.
|
|
30
|
+
- A repeated identical request returns the same approval. Do not send many requests to push a decision.
|
|
31
|
+
- To see the outcome later, read the release with `get_release`.
|
|
32
|
+
|
|
33
|
+
## Agent Runs
|
|
34
|
+
|
|
35
|
+
These routes carry no Ticket ID. When you hold a run, pass `runId` and the run's `taskId` so the server can check the run envelope. Reads use `READ_PROJECT_DATA`. Writes use `UPDATE_INTERNAL_WORKFLOW`.
|
|
@@ -9,8 +9,8 @@ Support cases stay linked to internal Tickets. Use `list_support_cases` to find
|
|
|
9
9
|
- Use `get_support_module` to read Support status and limits. The External API checks that the caller is a project owner or admin.
|
|
10
10
|
- Use `get_support_attachment_url` only when needed. Its short-lived private URL is sensitive. Do not put it in a comment or log.
|
|
11
11
|
- Read the latest case `revision` before a status change or reply. Use `update_support_case` for case status or priority. This does not change the internal Ticket status.
|
|
12
|
-
- Use `preview_support_reply` before a customer-visible reply. It checks send eligibility and returns the exact text, recipient display name, and delivery target. It does not send a message.
|
|
13
|
-
- Then call `reply_to_support_case` to send. Set `confirmSend` to `true` only for `REQUESTER_VISIBLE`. Omit it for `INTERNAL_NOTE`. Private notes do not send email.
|
|
12
|
+
- Use `preview_support_reply` before a customer-visible reply. It checks send eligibility and returns the exact text, recipient display name, and delivery target. It does not send a message. It also returns `preview.approval` (`required`, `policy` of `HUMAN_APPROVAL_REQUIRED` or `DIRECT_SEND_ALLOWED`, and `message`). Use it to know the outcome before you reply.
|
|
13
|
+
- Then call `reply_to_support_case`. Internal notes are immediate. A customer-visible reply needs human approval by default. In that case the tool returns `result: PENDING_APPROVAL` and `sent: false`. The reply is only a proposal. Do not tell anyone it was sent. Wait for a human decision in ToDoddle, then read `list_support_case_messages` to confirm. Do not retry with a new `idempotencyKey`. `confirmSend` does not bypass approval. Projects that opted in send directly. Set `confirmSend` to `true` only for `REQUESTER_VISIBLE`. Omit it for `INTERNAL_NOTE`. Private notes do not send email.
|
|
14
14
|
- Every reply needs a stable 8–120 character `idempotencyKey`. Reuse a key only to retry the same actor, content, visibility, and attachments. Reload after a revision conflict. Do not retry with a stale revision or a changed payload under the same key.
|
|
15
15
|
|
|
16
16
|
MCP does not return the requester's email address. For external requesters, the email is a temporary case-access notification; it does not contain the reply text. The support portal handles customer identity and access.
|