@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 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
- // Returns { ok, method, detail, hint? }.
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 { err, stdout } = await run(exec, bin, ["auth", "status"], stripParentSessionVars(env), timeoutMs);
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
- if (status.loggedIn === true) {
69
- return { ok: true, method: status.authMethod || "oauth", detail: status.email || status.authMethod || "logged in" };
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
- return { ok: false, method: "none", detail: "not logged in", hint: CLAUDE_LOGIN_HINT };
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
  }
@@ -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({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kendoo.agentdesk/agentdesk",
3
- "version": "0.34.0",
3
+ "version": "0.34.1",
4
4
  "description": "AI team orchestrator for Claude Code — run collaborative agent sessions from your terminal",
5
5
  "type": "module",
6
6
  "bin": {