@kendoo.agentdesk/agentdesk 0.18.1 → 0.18.3
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 +10 -0
- package/cli/init.mjs +2 -1
- package/cli/team.mjs +3 -1
- package/cli/tracker-check.mjs +61 -50
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,16 @@ All user-facing changes to AgentDesk. Each entry is tagged:
|
|
|
8
8
|
|
|
9
9
|
Internal refactors, infrastructure changes, and architectural notes are not listed here.
|
|
10
10
|
|
|
11
|
+
## [0.18.3] — 2026-04-18
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- `[CLI]` Tracker credentials: if a project has credentials stored both locally (`.env`) and on the server, the local value now wins. Previously the server's copy overrode `.env`, which meant that rotating a Jira / Linear / GitHub token locally didn't take effect while the server still held the old one.
|
|
15
|
+
|
|
16
|
+
## [0.18.2] — 2026-04-18
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- `[CLI]` Jira tracker check no longer blocks on a 404 from the `mypermissions` endpoint (some tenants don't expose it). Auth verification now lives in the `/myself` call; `mypermissions` is best-effort and only reports missing permissions if the tenant returns them. A 404 on `/myself` itself still fails loudly — that's a real configuration problem.
|
|
20
|
+
|
|
11
21
|
## [0.18.1] — 2026-04-18
|
|
12
22
|
|
|
13
23
|
### Fixed
|
package/cli/init.mjs
CHANGED
|
@@ -246,7 +246,8 @@ async function verifyAndPickTrackerProject({ rl, cwd, finalProjectKey, tracker,
|
|
|
246
246
|
const dotEnv = loadDotEnv(cwd);
|
|
247
247
|
const apiKey = loadApiKey(cwd);
|
|
248
248
|
const serverCreds = await fetchServerCreds(apiKey, finalProjectKey);
|
|
249
|
-
|
|
249
|
+
// .env wins over server credentials — fresh local rotation beats stale server state.
|
|
250
|
+
const credentials = resolveCredentialsFromEnv({ ...serverCreds, ...dotEnv });
|
|
250
251
|
const check = await checkTrackerPermissions({ tracker, config, credentials });
|
|
251
252
|
if (!check.ok) {
|
|
252
253
|
console.log(" ⚠ " + (check.errors[0] || "Verification failed"));
|
package/cli/team.mjs
CHANGED
|
@@ -112,7 +112,9 @@ export async function runTeam(taskId, opts = {}) {
|
|
|
112
112
|
} catch {}
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
// .env wins over server credentials — a freshly-rotated token in the
|
|
116
|
+
// user's project should beat a stale one left behind on the server.
|
|
117
|
+
const credentials = resolveCredentialsFromEnv({ ...serverCreds, ...projectEnvVars });
|
|
116
118
|
const check = await checkTrackerPermissions({ tracker, config, credentials });
|
|
117
119
|
|
|
118
120
|
if (!check.ok) {
|
package/cli/tracker-check.mjs
CHANGED
|
@@ -98,68 +98,79 @@ async function checkJira({ baseUrl, project }, creds) {
|
|
|
98
98
|
// produce `...net//rest/...` which some Atlassian paths reject.
|
|
99
99
|
const cleanBase = String(baseUrl || "").replace(/\/+$/, "");
|
|
100
100
|
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
101
|
+
// Primary check: /myself. If this works, the tenant + auth are fine.
|
|
102
|
+
// Try v3 first, fall back to v2 on 404. This is what we report to the
|
|
103
|
+
// user as "auth works"; mypermissions is now a best-effort follow-up.
|
|
104
|
+
let myselfRes = null;
|
|
105
|
+
let lastUrl = "";
|
|
106
|
+
for (const v of ["3", "2"]) {
|
|
107
|
+
lastUrl = `${cleanBase}/rest/api/${v}/myself`;
|
|
108
|
+
try {
|
|
109
|
+
myselfRes = await fetch(lastUrl, {
|
|
110
|
+
headers: { Authorization: auth, Accept: "application/json" },
|
|
111
|
+
signal: AbortSignal.timeout(10000),
|
|
112
|
+
});
|
|
113
|
+
if (myselfRes.status !== 404) break;
|
|
114
|
+
} catch (e) {
|
|
115
|
+
if (e.name === "AbortError" || e.name === "TimeoutError") {
|
|
116
|
+
return { ok: false, errors: [`Cannot reach Jira at ${cleanBase} — request timed out`] };
|
|
117
|
+
}
|
|
118
|
+
return { ok: false, errors: [`Cannot reach Jira at ${cleanBase}: ${e.message}`] };
|
|
110
119
|
}
|
|
120
|
+
}
|
|
121
|
+
if (!myselfRes) return { ok: false, errors: [`Cannot reach Jira at ${cleanBase}`] };
|
|
122
|
+
if (myselfRes.status === 401) {
|
|
123
|
+
return { ok: false, errors: ["Jira authentication failed — check JIRA_EMAIL and JIRA_API_TOKEN"] };
|
|
124
|
+
}
|
|
125
|
+
if (myselfRes.status === 403) {
|
|
126
|
+
return { ok: false, errors: ["Jira access forbidden — your account may lack access to this tenant"] };
|
|
127
|
+
}
|
|
128
|
+
if (myselfRes.status === 404) {
|
|
129
|
+
return { ok: false, errors: [`Jira API not found at ${cleanBase} (tried /rest/api/3/myself and /rest/api/2/myself). Confirm the base URL is the Atlassian tenant root and Jira is enabled on this tenant.`] };
|
|
130
|
+
}
|
|
131
|
+
if (!myselfRes.ok) {
|
|
132
|
+
return { ok: false, errors: [`Jira API error (${myselfRes.status}) calling ${lastUrl}`] };
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
const me = await myselfRes.json();
|
|
136
|
+
identity = { name: me.displayName || email, email: me.emailAddress || email, id: me.accountId };
|
|
111
137
|
} catch {}
|
|
112
138
|
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
const permissionsToCheck = "BROWSE_PROJECTS,CREATE_ISSUES,EDIT_ISSUES,ADD_COMMENTS,TRANSITION_ISSUES";
|
|
117
|
-
const tryVersions = ["3", "2"];
|
|
118
|
-
let res = null;
|
|
119
|
-
let lastUrl = "";
|
|
139
|
+
// Best-effort permission check. If it 404s or otherwise fails, we log the
|
|
140
|
+
// specific perms we COULD verify and return ok — the user's auth works;
|
|
141
|
+
// any missing permission will surface when the agent tries the operation.
|
|
120
142
|
try {
|
|
121
|
-
|
|
143
|
+
const permissionsToCheck = "BROWSE_PROJECTS,CREATE_ISSUES,EDIT_ISSUES,ADD_COMMENTS,TRANSITION_ISSUES";
|
|
144
|
+
let permRes = null;
|
|
145
|
+
for (const v of ["3", "2"]) {
|
|
122
146
|
const url = `${cleanBase}/rest/api/${v}/mypermissions?permissions=${permissionsToCheck}` +
|
|
123
147
|
(project ? `&projectKey=${project}` : "");
|
|
124
|
-
|
|
125
|
-
res = await fetch(url, {
|
|
148
|
+
permRes = await fetch(url, {
|
|
126
149
|
headers: { Authorization: auth, Accept: "application/json" },
|
|
127
150
|
signal: AbortSignal.timeout(10000),
|
|
128
151
|
});
|
|
129
|
-
if (
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (res.status === 401) {
|
|
133
|
-
return { ok: false, errors: ["Jira authentication failed — check JIRA_EMAIL and JIRA_API_TOKEN"] };
|
|
134
|
-
}
|
|
135
|
-
if (res.status === 403) {
|
|
136
|
-
return { ok: false, errors: ["Jira access forbidden — your account may lack access to this project"] };
|
|
137
|
-
}
|
|
138
|
-
if (!res.ok) {
|
|
139
|
-
return { ok: false, errors: [`Jira API error (${res.status}) calling ${lastUrl} — check that the base URL is the Atlassian tenant root (e.g. https://yourco.atlassian.net) without a path.`] };
|
|
152
|
+
if (permRes.status !== 404) break;
|
|
140
153
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
errors.push(`Missing permission: ${label} (${key})`);
|
|
154
|
+
if (permRes && permRes.ok) {
|
|
155
|
+
const data = await permRes.json();
|
|
156
|
+
const perms = data.permissions || {};
|
|
157
|
+
const permMap = {
|
|
158
|
+
BROWSE_PROJECTS: "read tasks",
|
|
159
|
+
CREATE_ISSUES: "create tasks",
|
|
160
|
+
EDIT_ISSUES: "update tasks",
|
|
161
|
+
ADD_COMMENTS: "add comments",
|
|
162
|
+
TRANSITION_ISSUES: "change task status",
|
|
163
|
+
};
|
|
164
|
+
for (const [key, label] of Object.entries(permMap)) {
|
|
165
|
+
if (perms[key] && !perms[key].havePermission) {
|
|
166
|
+
errors.push(`Missing permission: ${label} (${key})`);
|
|
167
|
+
}
|
|
156
168
|
}
|
|
157
169
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
return { ok: false, errors: [`Cannot reach Jira at ${cleanBase}: ${e.message}`] };
|
|
170
|
+
// If mypermissions failed, silently skip — agent will hit real errors
|
|
171
|
+
// when it tries an operation, which is more accurate anyway.
|
|
172
|
+
} catch {
|
|
173
|
+
// Ignore — auth already verified via /myself above.
|
|
163
174
|
}
|
|
164
175
|
|
|
165
176
|
return { ok: errors.length === 0, errors, identity };
|