draftgo-cli 4.0.1 → 4.0.23

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.
Files changed (79) hide show
  1. package/README.md +87 -11
  2. package/package.json +9 -4
  3. package/resources/custom-service-sdk/ai.go +520 -0
  4. package/resources/custom-service-sdk/ai_test.go +156 -0
  5. package/resources/custom-service-sdk/auth_test.go +56 -0
  6. package/resources/custom-service-sdk/billing.go +596 -0
  7. package/resources/custom-service-sdk/billing_test.go +150 -0
  8. package/resources/custom-service-sdk/go.mod +3 -0
  9. package/resources/custom-service-sdk/manifest.json +77 -0
  10. package/resources/custom-service-sdk/platform.go +345 -0
  11. package/resources/custom-service-sdk/platform_logger_test.go +24 -0
  12. package/resources/custom-service-sdk/registration_test.go +39 -0
  13. package/resources/custom-service-sdk/resources.go +246 -0
  14. package/resources/custom-service-sdk/resources_billing_test.go +115 -0
  15. package/resources/custom-service-sdk/resources_files_test.go +57 -0
  16. package/resources/custom-service-sdk/resources_scope_test.go +87 -0
  17. package/resources/custom-service-sdk/sdk.go +208 -0
  18. package/resources/skill/SKILL.md +36 -88
  19. package/resources/skill/init/SKILL.md +4 -4
  20. package/resources/skill/manifest.json +5 -1
  21. package/resources/skill/references/aihub.md +25 -2
  22. package/resources/skill/references/app-api.md +56 -6
  23. package/resources/skill/references/architecture.md +2 -2
  24. package/resources/skill/references/chat-sdk.md +4 -2
  25. package/resources/skill/references/checkout.md +17 -3
  26. package/resources/skill/references/custom-services.md +112 -47
  27. package/resources/skill/references/data.md +19 -4
  28. package/resources/skill/references/delivery.md +33 -0
  29. package/resources/skill/references/diagnostics.md +51 -0
  30. package/resources/skill/references/frontend.md +34 -46
  31. package/resources/skill/references/mcp.md +33 -5
  32. package/resources/skill/references/methods.md +189 -0
  33. package/resources/skill/references/modules.md +37 -9
  34. package/resources/skill/references/runtime.md +23 -1
  35. package/src/cli.js +24 -0
  36. package/src/commandRegistry.js +9 -1
  37. package/src/commands/api.js +21 -10
  38. package/src/commands/apiKey.js +34 -0
  39. package/src/commands/capabilities.js +93 -0
  40. package/src/commands/checkout.js +1 -1
  41. package/src/commands/commit.js +1 -1
  42. package/src/commands/components.js +550 -0
  43. package/src/commands/conflict.js +1 -1
  44. package/src/commands/connect.js +18 -8
  45. package/src/commands/customService.js +20 -4
  46. package/src/commands/dataRange.js +33 -0
  47. package/src/commands/delete.js +12 -1
  48. package/src/commands/diff.js +18 -2
  49. package/src/commands/grant.js +29 -0
  50. package/src/commands/group.js +38 -0
  51. package/src/commands/help.js +64 -20
  52. package/src/commands/init.js +3 -3
  53. package/src/commands/map.js +145 -17
  54. package/src/commands/mcp.js +2 -2
  55. package/src/commands/reconcile.js +1 -1
  56. package/src/commands/role.js +32 -0
  57. package/src/commands/space.js +41 -0
  58. package/src/commands/status.js +110 -7
  59. package/src/commands/update.js +23 -11
  60. package/src/commands/verify.js +75 -0
  61. package/src/commands/worklog.js +6 -2
  62. package/src/consoleEncoding.js +34 -0
  63. package/src/contractCompatibility.js +57 -0
  64. package/src/customServices.js +138 -18
  65. package/src/diffReport.js +106 -0
  66. package/src/index.js +2 -0
  67. package/src/localRuntime/compose.js +14 -17
  68. package/src/localRuntime/index.js +22 -23
  69. package/src/localRuntime/services.js +27 -36
  70. package/src/mcp/client.js +11 -2
  71. package/src/mcp/protocol.js +22 -2
  72. package/src/mcp/tools.js +14 -1
  73. package/src/platforms.js +9 -0
  74. package/src/projectConfig.js +6 -4
  75. package/src/releaseInstall.js +105 -0
  76. package/src/updateCheck.js +48 -28
  77. package/src/worklog.js +2 -1
  78. package/src/worktree/backend.js +1 -1
  79. package/src/worktree/index.js +7 -2
package/src/mcp/client.js CHANGED
@@ -184,7 +184,7 @@ class DraftGoMcpClient {
184
184
  constructor(config, options = {}) {
185
185
  if (!config || typeof config !== 'object') throw new TypeError('DraftGo MCP config is required.');
186
186
  const token = String(config.token || config.sat || '').trim();
187
- if (!token) throw new Error('DraftGo MCP config is missing SAT.');
187
+ if (!token) throw new Error('DraftGo MCP config is missing API Key.');
188
188
  if (!config.server && !config.mcp_url) throw new Error('DraftGo MCP config is missing server.');
189
189
 
190
190
  this.config = { ...config, token };
@@ -453,7 +453,7 @@ class DraftGoMcpClient {
453
453
  })).filter((entry) => entry.tool);
454
454
  const byCanonical = Object.fromEntries(selected.map((entry) => [entry.canonical, entry.tool]));
455
455
  const overrideCanonical = safeTools.find((canonical) => toolMatches(firstSafeTool.name, canonical));
456
- const defaultPlan = hasDiagnosticOverrides ? [{
456
+ let defaultPlan = hasDiagnosticOverrides ? [{
457
457
  label: overrideCanonical,
458
458
  canonical: overrideCanonical,
459
459
  tool: firstSafeTool,
@@ -473,6 +473,15 @@ class DraftGoMcpClient {
473
473
  args: { resource_type: 'db_meta', limit: 100 },
474
474
  },
475
475
  ];
476
+ const spaceScope = options.scopeType === 'space' && Number(options.spaceId) > 0
477
+ ? { scope_type: 'space', space_id: Number(options.spaceId) }
478
+ : {};
479
+ if (!hasDiagnosticOverrides && !spaceScope.scope_type) {
480
+ defaultPlan = defaultPlan.filter(entry => entry.label !== 'resource:docs/articles');
481
+ }
482
+ for (const entry of defaultPlan) {
483
+ if (entry.label === 'resource:docs/articles') entry.args = { ...entry.args, ...spaceScope };
484
+ }
476
485
  const runPlan = (plan, startIndex = 0) => allWithAbort(plan.map((entry, offset) =>
477
486
  async (queryOptions) => {
478
487
  if (typeof options.onStage === 'function') options.onStage(`tools/call:${entry.label}`, 'started');
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const DEFAULT_PROTOCOL_VERSION = '2025-06-18';
3
+ const DEFAULT_PROTOCOL_VERSION = '2026-07-28';
4
4
 
5
5
  class McpHttpError extends Error {
6
6
  constructor(message, { status = 0, rpc = null, code = null } = {}) {
@@ -34,7 +34,7 @@ function endpointFor(config) {
34
34
  ...((config.mcp_allowed_origins || []).map((value) => new URL(value).origin)),
35
35
  ]);
36
36
  if (!allowedOrigins.has(endpoint.origin)) {
37
- throw new McpHttpError('DraftGo MCP endpoint origin is not allowlisted; refusing to send the SAT.');
37
+ throw new McpHttpError('DraftGo MCP endpoint origin is not allowlisted; refusing to send the API Key.');
38
38
  }
39
39
  return endpoint.toString();
40
40
  }
@@ -44,6 +44,24 @@ function jsonRpcMessages(value) {
44
44
  return value && typeof value === 'object' ? [value] : [];
45
45
  }
46
46
 
47
+ function routingHeaders(message) {
48
+ const messages = jsonRpcMessages(message);
49
+ if (messages.length !== 1 || typeof messages[0].method !== 'string' || !messages[0].method) return {};
50
+ const request = messages[0];
51
+ const headers = { 'Mcp-Method': request.method };
52
+ const params = request.params && typeof request.params === 'object' && !Array.isArray(request.params)
53
+ ? request.params
54
+ : {};
55
+ if (request.method === 'tools/call' && typeof params.name === 'string' && params.name) {
56
+ headers['Mcp-Name'] = params.name;
57
+ } else if (request.method === 'prompts/get' && typeof params.name === 'string' && params.name) {
58
+ headers['Mcp-Name'] = params.name;
59
+ } else if (request.method === 'resources/read' && typeof params.uri === 'string' && params.uri) {
60
+ headers['Mcp-Name'] = params.uri;
61
+ }
62
+ return headers;
63
+ }
64
+
47
65
  async function parseEventStream(body, onMessage) {
48
66
  if (!body) return [];
49
67
  const reader = body.getReader();
@@ -113,6 +131,7 @@ async function postJsonRpc(config, message, options = {}) {
113
131
  Accept: 'application/json, text/event-stream',
114
132
  'Content-Type': 'application/json',
115
133
  'MCP-Protocol-Version': options.protocolVersion || DEFAULT_PROTOCOL_VERSION,
134
+ ...routingHeaders(message),
116
135
  };
117
136
  if (options.sessionId) headers['Mcp-Session-Id'] = options.sessionId;
118
137
 
@@ -169,6 +188,7 @@ module.exports = {
169
188
  McpHttpError,
170
189
  redactText,
171
190
  endpointFor,
191
+ routingHeaders,
172
192
  postJsonRpc,
173
193
  parseEventStream,
174
194
  };
package/src/mcp/tools.js CHANGED
@@ -35,6 +35,19 @@ async function openToolSession(config, expected = [], options = {}) {
35
35
  return { client, tools, names };
36
36
  }
37
37
 
38
+ function configuredScope(config, description = null) {
39
+ const operation = description && (description.operation || description) || {};
40
+ if (String(operation.ownership_mode || '').toLowerCase() === 'platform_only') return {};
41
+ const supported = Array.isArray(operation.supported_scopes)
42
+ ? operation.supported_scopes.map(value => String(value).toLowerCase()) : [];
43
+ const needsSpace = String(operation.ownership_mode || '').toLowerCase() === 'workspace_required'
44
+ && supported.length > 0 && !supported.includes('platform');
45
+ if (config && config.scope_type === 'space' && Number(config.space_id) > 0 && (needsSpace || supported.includes('space'))) {
46
+ return { scope_type: 'space', space_id: Number(config.space_id) };
47
+ }
48
+ return {};
49
+ }
50
+
38
51
  async function callStructured(session, expectedName, args = {}, options = {}) {
39
52
  const name = session.names[expectedName] || expectedName;
40
53
  const result = await session.client.toolsCall(name, args, options);
@@ -44,4 +57,4 @@ async function callStructured(session, expectedName, args = {}, options = {}) {
44
57
  : data;
45
58
  }
46
59
 
47
- module.exports = { TOOL_NAMES, openToolSession, callStructured };
60
+ module.exports = { TOOL_NAMES, openToolSession, callStructured, configuredScope };
package/src/platforms.js CHANGED
@@ -94,6 +94,15 @@ const platforms = [
94
94
  signals: ['.gemini', 'GEMINI.md'],
95
95
  frontmatter: { name: SKILL_NAME, description: DESCRIPTION },
96
96
  },
97
+ {
98
+ name: 'pi',
99
+ displayName: 'Pi',
100
+ mainFile: '.agents/skills/draftgo/SKILL.md',
101
+ assetDir: '.agents/skills/draftgo',
102
+ skillDir: '.agents/skills/draftgo',
103
+ signals: ['.agents/skills', '.pi'],
104
+ frontmatter: { name: SKILL_NAME, description: DESCRIPTION },
105
+ },
97
106
  ];
98
107
 
99
108
  const byName = Object.fromEntries(platforms.map((p) => [p.name, p]));
@@ -95,16 +95,18 @@ function writePrivateJson(file, value) {
95
95
 
96
96
  function writeProjectConfig(projectDir, server, token, options = {}) {
97
97
  const normalizedServer = normalizeServer(server);
98
- const sat = String(token || '').trim();
98
+ const apiKey = String(token || '').trim();
99
99
  if (!normalizedServer) throw new Error('DraftGo server is required.');
100
- if (!sat) throw new Error('DraftGo SAT is required.');
100
+ if (!apiKey) throw new Error('DraftGo API Key is required.');
101
101
  const existing = readExisting(projectDir);
102
102
  const config = {
103
103
  ...existing,
104
104
  server: normalizedServer,
105
- token: sat,
105
+ token: apiKey,
106
106
  auto_push: existing.auto_push === true,
107
107
  };
108
+ if (options.scope_type) config.scope_type = String(options.scope_type).trim().toLowerCase();
109
+ if (options.space_id) config.space_id = Number(options.space_id);
108
110
  if (options.mcp_url) config.mcp_url = normalizeMcpEndpoint(options.mcp_url);
109
111
  else delete config.mcp_url;
110
112
  const file = configPath(projectDir);
@@ -121,7 +123,7 @@ function loadProjectConfig(projectDir, { requireToken = true } = {}) {
121
123
  const mcpUrl = config.mcp_url ? normalizeMcpEndpoint(config.mcp_url) : '';
122
124
  const token = String(config.token || config.sat || '').trim();
123
125
  if (!server) throw new Error('DraftGo project config is missing server.');
124
- if (requireToken && !token) throw new Error('DraftGo project config is missing SAT.');
126
+ if (requireToken && !token) throw new Error('DraftGo project config is missing API Key.');
125
127
  const result = { ...config, server, token, path: file };
126
128
  if (mcpUrl) result.mcp_url = mcpUrl;
127
129
  else delete result.mcp_url;
@@ -0,0 +1,105 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+ const fs = require('fs');
5
+ const http = require('http');
6
+ const https = require('https');
7
+ const path = require('path');
8
+ const { spawnSync } = require('child_process');
9
+ const { PACKAGE_ROOT } = require('./paths');
10
+
11
+ const MARKER = '.draftgo-install.json';
12
+ const REPO = process.env.DRAFTGO_REPO || 'draftgo/draftgo-cli';
13
+ const BASE_URL = process.env.DRAFTGO_INSTALL_BASE_URL || 'https://draftgo.cn/draftgo-cli';
14
+ const MIRROR = process.env.DRAFTGO_GITHUB_MIRROR || `https://ghproxy.net/https://github.com/${REPO}/releases/download`;
15
+
16
+ function readInstallMarker() {
17
+ try {
18
+ const marker = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, MARKER), 'utf8'));
19
+ if (!marker.installRoot || !marker.binDir) return null;
20
+ return { installRoot: path.resolve(marker.installRoot), binDir: path.resolve(marker.binDir) };
21
+ } catch { return null; }
22
+ }
23
+
24
+ function download(url, output, timeoutMs = 15000, redirects = 0) {
25
+ return new Promise((resolve, reject) => {
26
+ if (redirects > 5) return reject(new Error('Too many download redirects.'));
27
+ const parsed = new URL(url);
28
+ const transport = parsed.protocol === 'http:' ? http : https;
29
+ const req = transport.get(parsed, { timeout: timeoutMs, headers: { 'user-agent': 'draftgo-cli' } }, (res) => {
30
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
31
+ res.resume();
32
+ return resolve(download(new URL(res.headers.location, parsed).toString(), output, timeoutMs, redirects + 1));
33
+ }
34
+ if (res.statusCode !== 200) { res.resume(); return reject(new Error(`HTTP ${res.statusCode}`)); }
35
+ const stream = fs.createWriteStream(output, { flags: 'wx' });
36
+ stream.on('error', reject);
37
+ res.on('error', reject);
38
+ stream.on('finish', () => stream.close(() => resolve(url)));
39
+ res.pipe(stream);
40
+ });
41
+ req.on('error', reject);
42
+ req.on('timeout', () => req.destroy(new Error('Download timed out.')));
43
+ });
44
+ }
45
+
46
+ async function downloadFirst(urls, output) {
47
+ const errors = [];
48
+ for (const url of urls) {
49
+ try { return { url: await download(url, output), output }; }
50
+ catch (error) { errors.push(`${url}: ${error.message}`); try { fs.rmSync(output, { force: true }); } catch {} }
51
+ }
52
+ throw new Error(`Unable to download DraftGo release.\n${errors.join('\n')}`);
53
+ }
54
+
55
+ function hashFile(file) {
56
+ return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
57
+ }
58
+
59
+ function writeLauncher(binDir, packageDir) {
60
+ fs.mkdirSync(binDir, { recursive: true });
61
+ if (process.platform === 'win32') {
62
+ const launcher = path.join(binDir, 'draftgo.cmd');
63
+ fs.writeFileSync(launcher, `@echo off\r\nnode "${path.join(packageDir, 'bin', 'draftgo.js')}" %*\r\n`, 'ascii');
64
+ return launcher;
65
+ }
66
+ const launcher = path.join(binDir, 'draftgo');
67
+ fs.writeFileSync(launcher, `#!/usr/bin/env sh\nexec node "${path.join(packageDir, 'bin', 'draftgo.js')}" "$@"\n`, { mode: 0o755 });
68
+ fs.chmodSync(launcher, 0o755);
69
+ return launcher;
70
+ }
71
+
72
+ async function installRelease(version, expectedSha, marker = readInstallMarker()) {
73
+ if (!marker) return null;
74
+ if (!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(String(version))) throw new Error('Invalid release version.');
75
+ fs.mkdirSync(marker.installRoot, { recursive: true });
76
+ const tempDir = fs.mkdtempSync(path.join(marker.installRoot, '.update-'));
77
+ const archive = path.join(tempDir, `draftgo-cli-${version}.tgz`);
78
+ const asset = path.basename(archive);
79
+ const urls = [`${BASE_URL}/${asset}`, `${MIRROR}/v${version}/${asset}`, `https://github.com/${REPO}/releases/download/v${version}/${asset}`];
80
+ try {
81
+ const downloaded = await downloadFirst(urls, archive);
82
+ let checksum = expectedSha;
83
+ if (!checksum) {
84
+ const checksumFile = path.join(tempDir, `${asset}.sha256`);
85
+ await download(`${downloaded.url}.sha256`, checksumFile);
86
+ checksum = fs.readFileSync(checksumFile, 'utf8').trim().split(/\s+/)[0];
87
+ }
88
+ if (!/^[0-9a-f]{64}$/i.test(String(checksum)) || hashFile(archive).toLowerCase() !== checksum.toLowerCase()) {
89
+ throw new Error('DraftGo archive checksum mismatch.');
90
+ }
91
+ const staged = path.join(tempDir, 'package');
92
+ fs.mkdirSync(staged);
93
+ const extract = spawnSync('tar', ['-xzf', archive, '-C', staged, '--strip-components=1'], { encoding: 'utf8', shell: false });
94
+ if (extract.error || extract.status !== 0) throw new Error(extract.error ? extract.error.message : (extract.stderr || 'Unable to extract DraftGo release.'));
95
+ const installedVersion = JSON.parse(fs.readFileSync(path.join(staged, 'package.json'), 'utf8')).version;
96
+ if (installedVersion !== version) throw new Error(`Release version mismatch: expected ${version}, received ${installedVersion}.`);
97
+ fs.writeFileSync(path.join(staged, MARKER), JSON.stringify(marker, null, 2) + '\n');
98
+ const destination = path.join(marker.installRoot, version);
99
+ if (fs.existsSync(destination)) fs.rmSync(destination, { recursive: true, force: true });
100
+ fs.renameSync(staged, destination);
101
+ return { launcher: writeLauncher(marker.binDir, destination), destination };
102
+ } finally { fs.rmSync(tempDir, { recursive: true, force: true }); }
103
+ }
104
+
105
+ module.exports = { MARKER, readInstallMarker, installRelease, hashFile, writeLauncher };
@@ -1,14 +1,20 @@
1
1
  'use strict';
2
2
 
3
- // Fetch the latest draftgo-cli version from the npm registry.
3
+ // Fetch the latest draftgo-cli version from the DraftGo release endpoint.
4
4
  // Results are cached for 24 hours so update stays fast and deterministic.
5
5
 
6
+ const http = require('http');
6
7
  const https = require('https');
7
8
  const fs = require('fs');
8
9
  const os = require('os');
9
10
  const path = require('path');
10
11
 
11
- const REGISTRY_URL = 'https://registry.npmjs.org/draftgo-cli/latest';
12
+ const REPO = process.env.DRAFTGO_REPO || 'draftgo/draftgo-cli';
13
+ const RELEASE_BASE_URL = process.env.DRAFTGO_INSTALL_BASE_URL || 'https://draftgo.cn/draftgo-cli';
14
+ const RELEASE_URLS = [
15
+ `${RELEASE_BASE_URL}/latest.json`,
16
+ `https://api.github.com/repos/${REPO}/releases/latest`,
17
+ ];
12
18
  const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
13
19
  const CACHE_FILE = path.join(os.homedir(), '.draftgo-cli', 'update-check.json');
14
20
 
@@ -32,40 +38,54 @@ function writeCache(version) {
32
38
  }
33
39
  }
34
40
 
35
- function fetchLatestVersion(timeoutMs = 3000, opts = {}) {
36
- if (!opts.force) {
37
- const cached = readCache();
38
- if (cached) return Promise.resolve(cached);
39
- }
41
+ function requestVersion(url, timeoutMs, redirects = 0) {
40
42
  return new Promise((resolve) => {
43
+ if (redirects > 5) return resolve(null);
44
+ let parsed;
45
+ try { parsed = new URL(url); } catch { return resolve(null); }
46
+ const transport = parsed.protocol === 'http:' ? http : https;
41
47
  let req;
42
48
  try {
43
- req = https.get(
44
- REGISTRY_URL,
45
- { timeout: timeoutMs, headers: { accept: 'application/json' } },
46
- (res) => {
47
- if (res.statusCode !== 200) { res.resume(); return resolve(null); }
48
- let body = '';
49
- res.setEncoding('utf8');
50
- res.on('data', (c) => { body += c; });
51
- res.on('end', () => {
52
- try {
53
- const version = JSON.parse(body).version || null;
54
- if (version) writeCache(version);
55
- resolve(version);
56
- }
57
- catch { resolve(null); }
58
- });
49
+ req = transport.get(parsed, {
50
+ timeout: timeoutMs,
51
+ headers: { accept: 'application/json', 'user-agent': 'draftgo-cli' },
52
+ }, (res) => {
53
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
54
+ res.resume();
55
+ return resolve(requestVersion(new URL(res.headers.location, parsed).toString(), timeoutMs, redirects + 1));
59
56
  }
60
- );
61
- } catch {
62
- return resolve(null);
63
- }
57
+ if (res.statusCode !== 200) { res.resume(); return resolve(null); }
58
+ let body = '';
59
+ res.setEncoding('utf8');
60
+ res.on('data', (chunk) => {
61
+ body += chunk;
62
+ if (body.length > 64 * 1024) req.destroy();
63
+ });
64
+ res.on('end', () => {
65
+ try {
66
+ const payload = JSON.parse(body);
67
+ resolve(payload.version || String(payload.tag_name || '').replace(/^v/, '') || null);
68
+ } catch { resolve(null); }
69
+ });
70
+ });
71
+ } catch { return resolve(null); }
64
72
  req.on('error', () => resolve(null));
65
73
  req.on('timeout', () => { req.destroy(); resolve(null); });
66
74
  });
67
75
  }
68
76
 
77
+ async function fetchLatestVersion(timeoutMs = 3000, opts = {}) {
78
+ if (!opts.force) {
79
+ const cached = readCache();
80
+ if (cached) return Promise.resolve(cached);
81
+ }
82
+ for (const url of RELEASE_URLS) {
83
+ const version = await requestVersion(url, timeoutMs);
84
+ if (version) { writeCache(version); return version; }
85
+ }
86
+ return null;
87
+ }
88
+
69
89
  // Compare 'a.b.c' ignoring prerelease tags. Returns 1 / 0 / -1.
70
90
  function cmpSemver(a, b) {
71
91
  const pa = String(a).split('-')[0].split('.').map((x) => Number(x) || 0);
@@ -77,4 +97,4 @@ function cmpSemver(a, b) {
77
97
  return 0;
78
98
  }
79
99
 
80
- module.exports = { fetchLatestVersion, cmpSemver, readCache, CACHE_TTL_MS };
100
+ module.exports = { fetchLatestVersion, cmpSemver, readCache, requestVersion, CACHE_TTL_MS };
package/src/worklog.js CHANGED
@@ -215,7 +215,8 @@ function acquireLock(destination, options = {}) {
215
215
  if (descriptor !== undefined) {
216
216
  try { fs.closeSync(descriptor); } catch { /* Preserve the original failure. */ }
217
217
  }
218
- if (!error || error.code !== 'EEXIST') throw error;
218
+ const retryable = error && ['EEXIST', 'EPERM', 'EACCES', 'EBUSY'].includes(error.code);
219
+ if (!retryable) throw error;
219
220
  }
220
221
  if (removeStaleLock(lockPath, staleMs)) continue;
221
222
  const elapsed = Date.now() - started;
@@ -156,7 +156,7 @@ function normalizeUrl(config, value, purpose) {
156
156
  if (!allowed.has(url.origin)) {
157
157
  throw new WorktreeError(
158
158
  'UNTRUSTED_BACKEND_ORIGIN',
159
- `${purpose} URL origin is not allowlisted; refusing to send the SAT.`,
159
+ `${purpose} URL origin is not allowlisted; refusing to send the API Key.`,
160
160
  { origin: url.origin },
161
161
  );
162
162
  }
@@ -491,11 +491,16 @@ async function commitResources(projectDir, resourceType, resourceIds, options =
491
491
  const failures = settled.filter((item) => item.status === 'rejected');
492
492
  if (failures.length) {
493
493
  const first = failures[0].reason;
494
+ const inputOrder = new Map(ids.map((id, index) => [String(id), index]));
495
+ const ordered = (items) => [...items].sort((left, right) => (
496
+ (inputOrder.get(String(left.resource_id)) ?? Number.MAX_SAFE_INTEGER)
497
+ - (inputOrder.get(String(right.resource_id)) ?? Number.MAX_SAFE_INTEGER)
498
+ ));
494
499
  first.details = {
495
500
  ...(first.details || {}),
496
501
  batch: {
497
- completed: results.filter((item) => ['committed', 'unchanged'].includes(item.status)),
498
- failed: results.filter((item) => item.status === 'failed'),
502
+ completed: ordered(results.filter((item) => ['committed', 'unchanged'].includes(item.status))),
503
+ failed: ordered(results.filter((item) => item.status === 'failed')),
499
504
  not_started: [],
500
505
  },
501
506
  };