@kendoo.agentdesk/agentdesk 0.34.0 → 0.34.1
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 +5 -0
- package/README.md +1 -1
- package/cli/engine/claude-auth.mjs +53 -6
- package/cli/engine/session.mjs +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,11 @@ 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.34.1] — 2026-09-21
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- `[CLI]` The Claude login check before a session now sends one tiny real request instead of trusting `claude auth status`. An expired or revoked login fails the session before INTAKE and names the profile to reconnect (for example `CLAUDE_CONFIG_DIR=<profile> claude auth login`), instead of surfacing as a 401 from inside the first phase. Set `AGENTDESK_SKIP_AUTH_PROBE=1` to skip the probe.
|
|
15
|
+
|
|
11
16
|
## [0.34.0] — 2026-09-21
|
|
12
17
|
|
|
13
18
|
### Added
|
package/README.md
CHANGED
|
@@ -294,7 +294,7 @@ Run `npm run test:sdk-leadership` to check Jane's decisions against supplied rep
|
|
|
294
294
|
|
|
295
295
|
Sessions started from the dashboard with an updated daemon can be paused and resumed in the same conversation and workspace. Use **Pause / check workers**, or enter a correction and choose **Pause & save**. Instructions are saved immediately; **Verify & resume** becomes available after the previous workers have stopped and released the workspace. A quarantined workspace remains blocked until its workers exit.
|
|
296
296
|
|
|
297
|
-
Resume restores the original task, discovered tracker task ID, pending phase, saved findings, user corrections, token totals, and recorded external actions. Corrections trigger replanning against the existing work. Access is checked again before continuing; a failed login check preserves both the checkpoint and the pending correction. For a revoked model login, reconnect Claude on the daemon computer using the same account profile, then choose **Verify & resume**. Session controls require the session owner and a connected daemon that supports recovery; older sessions need a separate continuation.
|
|
297
|
+
Resume restores the original task, discovered tracker task ID, pending phase, saved findings, user corrections, token totals, and recorded external actions. Corrections trigger replanning against the existing work. Access is checked again before continuing; a failed login check preserves both the checkpoint and the pending correction. For a revoked model login, reconnect Claude on the daemon computer using the same account profile, then choose **Verify & resume**. Before every session the engine checks that login with one tiny real request; an expired or revoked login stops the session before intake and names the profile to reconnect. Session controls require the session owner and a connected daemon that supports recovery; older sessions need a separate continuation.
|
|
298
298
|
|
|
299
299
|
A missing phase handoff gets one repair attempt using the preserved findings with execution tools disabled. If repair fails, or required tracker access is denied during intake or planning, work pauses for intervention. Recognized external write commands are fingerprinted before dispatch and exact repeats are blocked. An uncertain result must be reconciled with the provider before continuing. This is a conservative replay guard, not general deduplication of equivalent commands or arbitrary scripts.
|
|
300
300
|
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import { execFile } from "child_process";
|
|
13
13
|
import { createRequire } from "node:module";
|
|
14
|
+
import { homedir } from "os";
|
|
14
15
|
import { dirname, join } from "path";
|
|
15
16
|
import { stripParentSessionVars } from "./env.mjs";
|
|
16
17
|
|
|
@@ -36,6 +37,26 @@ export const CLAUDE_LOGIN_HINT = [
|
|
|
36
37
|
" • or add ANTHROPIC_API_KEY=<key> to the project's .env.",
|
|
37
38
|
].join("\n");
|
|
38
39
|
|
|
40
|
+
// The profile a standalone child will authenticate with: the one `claude auth
|
|
41
|
+
// status` reports, else the selected CLAUDE_CONFIG_DIR, else the default.
|
|
42
|
+
export function profileInUse(env = {}, status = null) {
|
|
43
|
+
return status?.configDirectory || env.CLAUDE_CONFIG_DIR || "~/.claude (default profile)";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// The hint names the profile and the exact login command for it, because a
|
|
47
|
+
// machine with several profiles can be logged in everywhere except the one
|
|
48
|
+
// the daemon runs with.
|
|
49
|
+
export function loginHint(profile) {
|
|
50
|
+
const isDefault = !profile.startsWith("/") || profile === join(homedir(), ".claude");
|
|
51
|
+
const login = isDefault ? "claude auth login" : `CLAUDE_CONFIG_DIR=${profile} claude auth login`;
|
|
52
|
+
return `${CLAUDE_LOGIN_HINT}\nProfile in use: ${profile} — reconnect it on the daemon machine with \`${login}\`.`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Probe error text that means the stored login itself cannot be used (as
|
|
56
|
+
// opposed to a transient API problem, which must not block a session).
|
|
57
|
+
const AUTH_FAILURE = /authenticat|oauth|\b401\b|not logged in|invalid api key|api key/i;
|
|
58
|
+
const PROBE_ARGS = ["-p", "Reply with exactly: OK", "--max-turns", "1", "--output-format", "json"];
|
|
59
|
+
|
|
39
60
|
function run(exec, bin, args, env, timeoutMs) {
|
|
40
61
|
return new Promise(resolve => {
|
|
41
62
|
exec(bin, args, { env, timeout: timeoutMs, maxBuffer: 1 << 20 }, (err, stdout, stderr) => {
|
|
@@ -44,14 +65,29 @@ function run(exec, bin, args, env, timeoutMs) {
|
|
|
44
65
|
});
|
|
45
66
|
}
|
|
46
67
|
|
|
47
|
-
//
|
|
68
|
+
// `claude auth status` only reports that credentials exist; it does not use
|
|
69
|
+
// them. An expired or revoked OAuth token passes it and then fails the first
|
|
70
|
+
// real request from inside INTAKE. One tiny real request settles it up front.
|
|
71
|
+
async function probeLogin(exec, bin, env, timeoutMs) {
|
|
72
|
+
const { err, stdout } = await run(exec, bin, PROBE_ARGS, env, timeoutMs);
|
|
73
|
+
let result = null;
|
|
74
|
+
try { result = JSON.parse(stdout); } catch {}
|
|
75
|
+
if (result && typeof result === "object") {
|
|
76
|
+
if (result.is_error) return { verdict: "error", message: String(result.result || result.subtype || "error result") };
|
|
77
|
+
return { verdict: "ok" };
|
|
78
|
+
}
|
|
79
|
+
return { verdict: "inconclusive", message: err?.message || (stdout ? "unexpected probe output" : "no output") };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Returns { ok, method, detail, hint?, warning? }.
|
|
48
83
|
// env — the environment the session child will get (dotenv + sandbox applied)
|
|
49
84
|
// exec — injectable for tests (child_process.execFile signature)
|
|
50
|
-
export async function checkClaudeAuth({ env = process.env, exec = execFile, timeoutMs = 10000 } = {}) {
|
|
85
|
+
export async function checkClaudeAuth({ env = process.env, exec = execFile, timeoutMs = 10000, probeTimeoutMs = 30000 } = {}) {
|
|
51
86
|
if (env.ANTHROPIC_API_KEY) return { ok: true, method: "api-key", detail: "ANTHROPIC_API_KEY" };
|
|
52
87
|
|
|
53
88
|
const bin = claudeBinary(env);
|
|
54
|
-
const
|
|
89
|
+
const childEnv = stripParentSessionVars(env);
|
|
90
|
+
const { err, stdout } = await run(exec, bin, ["auth", "status"], childEnv, timeoutMs);
|
|
55
91
|
|
|
56
92
|
if (err && !stdout) {
|
|
57
93
|
const detail = err.code === "ENOENT"
|
|
@@ -65,8 +101,19 @@ export async function checkClaudeAuth({ env = process.env, exec = execFile, time
|
|
|
65
101
|
return { ok: false, method: "unknown", detail: "unexpected output from `claude auth status`", hint: CLAUDE_LOGIN_HINT };
|
|
66
102
|
}
|
|
67
103
|
|
|
68
|
-
|
|
69
|
-
|
|
104
|
+
const profile = profileInUse(env, status);
|
|
105
|
+
if (status.loggedIn !== true) return { ok: false, method: "none", detail: `not logged in (profile: ${profile})`, hint: loginHint(profile) };
|
|
106
|
+
|
|
107
|
+
const method = status.authMethod || "oauth";
|
|
108
|
+
const detail = status.email || method;
|
|
109
|
+
if (env.AGENTDESK_SKIP_AUTH_PROBE === "1") return { ok: true, method, detail };
|
|
110
|
+
|
|
111
|
+
const probe = await probeLogin(exec, bin, childEnv, probeTimeoutMs);
|
|
112
|
+
if (probe.verdict === "ok") return { ok: true, method, detail };
|
|
113
|
+
if (probe.verdict === "error" && AUTH_FAILURE.test(probe.message)) {
|
|
114
|
+
return { ok: false, method, detail: `Claude login for ${profile} cannot be used: ${probe.message}`, hint: loginHint(profile) };
|
|
70
115
|
}
|
|
71
|
-
|
|
116
|
+
// A transient API problem or an odd CLI answer is not a login failure:
|
|
117
|
+
// let the session start and say what the probe saw.
|
|
118
|
+
return { ok: true, method, detail, warning: `login probe ${probe.verdict === "error" ? "failed" : "inconclusive"} (${probe.message}) — continuing` };
|
|
72
119
|
}
|
package/cli/engine/session.mjs
CHANGED
|
@@ -220,6 +220,7 @@ async function executeSession({
|
|
|
220
220
|
const auth = await authCheck({ env: buildChildEnv({ dotenv }) });
|
|
221
221
|
abortSignal?.throwIfAborted();
|
|
222
222
|
if (!auth.ok) return failStart("CLAUDE_NOT_LOGGED_IN", `${auth.detail}.\n${auth.hint || ""}`.trim());
|
|
223
|
+
if (auth.warning) emit({ type: "agent:message", agent: "Jane", tag: "SAY", message: `Claude login check: ${auth.warning}.` });
|
|
223
224
|
emit({ type: "session:recovery", recovery: { state: "running", kind: "resume", message: "Access checked; continuing the saved task.", ready: false } });
|
|
224
225
|
|
|
225
226
|
const sandbox = createScratchHome({
|