@songsid/agend 2.2.0-beta.2 → 2.2.0-beta.20
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/README.md +40 -24
- package/README.zh-TW.md +40 -24
- package/dist/agent-cli-instructions.md +6 -2
- package/dist/agent-cli.js +15 -7
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.js +3 -0
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.js +16 -4
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/auth-api.d.ts +3 -1
- package/dist/auth-api.js +17 -1
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/antigravity.d.ts +33 -0
- package/dist/backend/antigravity.js +126 -18
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-carry.d.ts +22 -0
- package/dist/backend/claude-carry.js +170 -0
- package/dist/backend/claude-carry.js.map +1 -0
- package/dist/backend/claude-code.d.ts +65 -2
- package/dist/backend/claude-code.js +226 -33
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex-metadata.d.ts +33 -0
- package/dist/backend/codex-metadata.js +122 -18
- package/dist/backend/codex-metadata.js.map +1 -1
- package/dist/backend/codex.d.ts +3 -0
- package/dist/backend/codex.js +17 -1
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/credential-profile.d.ts +40 -3
- package/dist/backend/credential-profile.js +113 -5
- package/dist/backend/credential-profile.js.map +1 -1
- package/dist/backend/effort-metadata.d.ts +37 -1
- package/dist/backend/effort-metadata.js +86 -15
- package/dist/backend/effort-metadata.js.map +1 -1
- package/dist/backend/grok.d.ts +4 -0
- package/dist/backend/grok.js +14 -0
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro.d.ts +27 -1
- package/dist/backend/kiro.js +63 -10
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/launch-effort.d.ts +61 -0
- package/dist/backend/launch-effort.js +170 -0
- package/dist/backend/launch-effort.js.map +1 -0
- package/dist/backend/muse.d.ts +84 -0
- package/dist/backend/muse.js +449 -53
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +48 -2
- package/dist/backend/types.js +12 -1
- package/dist/backend/types.js.map +1 -1
- package/dist/backend-command.d.ts +91 -0
- package/dist/backend-command.js +122 -0
- package/dist/backend-command.js.map +1 -0
- package/dist/background-work-ended.d.ts +18 -0
- package/dist/background-work-ended.js +30 -0
- package/dist/background-work-ended.js.map +1 -0
- package/dist/cache-service.d.ts +2 -0
- package/dist/cache-service.js +2 -1
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +26 -1
- package/dist/channel/adapters/discord.js +103 -3
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +35 -3
- package/dist/channel/adapters/telegram.js +122 -23
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-tools.js +5 -3
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/message-edit-errors.d.ts +5 -0
- package/dist/channel/message-edit-errors.js +24 -0
- package/dist/channel/message-edit-errors.js.map +1 -0
- package/dist/channel/telegram-markdown.d.ts +6 -0
- package/dist/channel/telegram-markdown.js +104 -0
- package/dist/channel/telegram-markdown.js.map +1 -0
- package/dist/channel/tool-router.d.ts +2 -0
- package/dist/channel/tool-router.js +6 -5
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/channel/types.d.ts +46 -4
- package/dist/cli.js +184 -34
- package/dist/cli.js.map +1 -1
- package/dist/command-table.d.ts +2 -2
- package/dist/command-table.js +5 -2
- package/dist/command-table.js.map +1 -1
- package/dist/compact-watch.d.ts +86 -0
- package/dist/compact-watch.js +288 -0
- package/dist/compact-watch.js.map +1 -0
- package/dist/config-validator.js +55 -5
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/dist/cross-instance-envelope.js +2 -0
- package/dist/cross-instance-envelope.js.map +1 -1
- package/dist/daemon.d.ts +129 -1
- package/dist/daemon.js +754 -94
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-outbox.d.ts +64 -8
- package/dist/delivery-outbox.js +315 -24
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/delivery-reconciliation.d.ts +18 -1
- package/dist/delivery-reconciliation.js +67 -2
- package/dist/delivery-reconciliation.js.map +1 -1
- package/dist/doctor.js +7 -0
- package/dist/doctor.js.map +1 -1
- package/dist/fleet-context.d.ts +12 -1
- package/dist/fleet-manager.d.ts +392 -19
- package/dist/fleet-manager.js +2030 -275
- package/dist/fleet-manager.js.map +1 -1
- package/dist/forged-envelope-scan.d.ts +4 -3
- package/dist/forged-envelope-scan.js +4 -3
- package/dist/forged-envelope-scan.js.map +1 -1
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +84 -16
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +16 -1
- package/dist/general-knowledge/skills/model-discovery/SKILL.md +4 -1
- package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +4 -0
- package/dist/host-memory.d.ts +10 -0
- package/dist/host-memory.js +33 -1
- package/dist/host-memory.js.map +1 -1
- package/dist/human-delivery-receipts.d.ts +83 -0
- package/dist/human-delivery-receipts.js +194 -0
- package/dist/human-delivery-receipts.js.map +1 -0
- package/dist/install-lock.d.ts +18 -1
- package/dist/install-lock.js +30 -3
- package/dist/install-lock.js.map +1 -1
- package/dist/instance-avatar.d.ts +5 -4
- package/dist/instance-avatar.js +12 -8
- package/dist/instance-avatar.js.map +1 -1
- package/dist/instance-config-impact.js +1 -0
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +25 -6
- package/dist/instance-lifecycle.js +76 -27
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.d.ts +1 -1
- package/dist/instructions.js +3 -3
- package/dist/instructions.js.map +1 -1
- package/dist/kiro-compact-pane.d.ts +38 -0
- package/dist/kiro-compact-pane.js +57 -0
- package/dist/kiro-compact-pane.js.map +1 -0
- package/dist/kiro-log-gate.d.ts +42 -0
- package/dist/kiro-log-gate.js +39 -0
- package/dist/kiro-log-gate.js.map +1 -0
- package/dist/launchd-handoff.d.ts +43 -0
- package/dist/launchd-handoff.js +56 -0
- package/dist/launchd-handoff.js.map +1 -0
- package/dist/locale.js +268 -42
- package/dist/locale.js.map +1 -1
- package/dist/log-service.d.ts +99 -0
- package/dist/log-service.js +243 -0
- package/dist/log-service.js.map +1 -0
- package/dist/log-steps.d.ts +169 -0
- package/dist/log-steps.js +858 -0
- package/dist/log-steps.js.map +1 -0
- package/dist/login-controller.d.ts +20 -3
- package/dist/login-controller.js +71 -19
- package/dist/login-controller.js.map +1 -1
- package/dist/login-flows.d.ts +5 -2
- package/dist/login-flows.js +5 -4
- package/dist/login-flows.js.map +1 -1
- package/dist/login-shell.d.ts +21 -0
- package/dist/login-shell.js +98 -0
- package/dist/login-shell.js.map +1 -0
- package/dist/memory-pressure.d.ts +13 -1
- package/dist/memory-pressure.js +50 -10
- package/dist/memory-pressure.js.map +1 -1
- package/dist/needs-you-hub.js +2 -0
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you-live.d.ts +6 -0
- package/dist/needs-you-live.js +23 -3
- package/dist/needs-you-live.js.map +1 -1
- package/dist/outbound-handlers.d.ts +100 -0
- package/dist/outbound-handlers.js +277 -93
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +30 -15
- package/dist/outbound-schemas.js +23 -5
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/provider-probe.d.ts +29 -0
- package/dist/provider-probe.js +198 -2
- package/dist/provider-probe.js.map +1 -1
- package/dist/public-link-progress.d.ts +1 -1
- package/dist/public-link-progress.js +8 -2
- package/dist/public-link-progress.js.map +1 -1
- package/dist/public-preview.d.ts +101 -0
- package/dist/public-preview.js +236 -0
- package/dist/public-preview.js.map +1 -0
- package/dist/public-web-gateway.d.ts +2 -0
- package/dist/public-web-gateway.js +20 -6
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +24 -1
- package/dist/public-web-link.js +63 -9
- package/dist/public-web-link.js.map +1 -1
- package/dist/quickstart-api.d.ts +1 -1
- package/dist/quickstart-api.js +8 -6
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.d.ts +12 -0
- package/dist/quickstart.js +36 -6
- package/dist/quickstart.js.map +1 -1
- package/dist/reply-buttons-controller.d.ts +89 -5
- package/dist/reply-buttons-controller.js +273 -8
- package/dist/reply-buttons-controller.js.map +1 -1
- package/dist/reply-buttons.d.ts +48 -3
- package/dist/reply-buttons.js +105 -10
- package/dist/reply-buttons.js.map +1 -1
- package/dist/reply-polls.d.ts +85 -0
- package/dist/reply-polls.js +154 -0
- package/dist/reply-polls.js.map +1 -0
- package/dist/restart-guard.d.ts +6 -3
- package/dist/restart-guard.js +6 -3
- package/dist/restart-guard.js.map +1 -1
- package/dist/restart-progress.d.ts +33 -2
- package/dist/restart-progress.js +30 -2
- package/dist/restart-progress.js.map +1 -1
- package/dist/sd-notify.d.ts +22 -1
- package/dist/sd-notify.js +63 -10
- package/dist/sd-notify.js.map +1 -1
- package/dist/service-activation.d.ts +15 -7
- package/dist/service-activation.js +42 -26
- package/dist/service-activation.js.map +1 -1
- package/dist/service-installer.d.ts +55 -0
- package/dist/service-installer.js +90 -3
- package/dist/service-installer.js.map +1 -1
- package/dist/service-plan.d.ts +11 -0
- package/dist/service-plan.js +23 -0
- package/dist/service-plan.js.map +1 -1
- package/dist/settings-api.js +14 -4
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +4 -2
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +2 -0
- package/dist/settings-effect.js.map +1 -1
- package/dist/spawn-gate.js +5 -1
- package/dist/spawn-gate.js.map +1 -1
- package/dist/spawn-hold-reply.d.ts +12 -0
- package/dist/spawn-hold-reply.js +39 -0
- package/dist/spawn-hold-reply.js.map +1 -0
- package/dist/systemd-peer-bus.d.ts +95 -0
- package/dist/systemd-peer-bus.js +430 -0
- package/dist/systemd-peer-bus.js.map +1 -0
- package/dist/systemd-runtime.d.ts +27 -1
- package/dist/systemd-runtime.js +69 -7
- package/dist/systemd-runtime.js.map +1 -1
- package/dist/tips.js +1 -1
- package/dist/tips.js.map +1 -1
- package/dist/tool-permissions.js +3 -3
- package/dist/tool-permissions.js.map +1 -1
- package/dist/tool-progress.js +3 -2
- package/dist/tool-progress.js.map +1 -1
- package/dist/topic-commands.d.ts +71 -4
- package/dist/topic-commands.js +167 -34
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-delta-reader.d.ts +3 -3
- package/dist/transcript-delta-reader.js +8 -8
- package/dist/transcript-delta-reader.js.map +1 -1
- package/dist/transcript-jsonl.d.ts +85 -3
- package/dist/transcript-jsonl.js +156 -13
- package/dist/transcript-jsonl.js.map +1 -1
- package/dist/transcript-monitor.d.ts +11 -0
- package/dist/transcript-monitor.js +61 -13
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-sources.d.ts +24 -0
- package/dist/transcript-sources.js +211 -56
- package/dist/transcript-sources.js.map +1 -1
- package/dist/tunnel/cloudflared.d.ts +7 -15
- package/dist/tunnel/cloudflared.js +36 -1
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/tunnel/purpose-lane.d.ts +5 -1
- package/dist/tunnel/purpose-lane.js +14 -7
- package/dist/tunnel/purpose-lane.js.map +1 -1
- package/dist/tunnel/types.d.ts +5 -1
- package/dist/tunnel/types.js.map +1 -1
- package/dist/turn-resume.d.ts +2 -0
- package/dist/turn-resume.js +1 -1
- package/dist/turn-resume.js.map +1 -1
- package/dist/types.d.ts +22 -0
- package/dist/ui/chat-commands.js +1 -0
- package/dist/ui/chat-mentions.js +63 -0
- package/dist/ui/chat-render.js +80 -10
- package/dist/ui/chat-store.js +70 -6
- package/dist/ui/chat-strings.js +62 -32
- package/dist/ui/chat-thread.js +199 -24
- package/dist/ui/chat-time.js +80 -0
- package/dist/ui/chat-tour.js +17 -14
- package/dist/ui/panel-chat.js +45 -21
- package/dist/ui/panel-details.js +2 -0
- package/dist/ui/panel-fleet.js +213 -58
- package/dist/ui/panel-log.js +428 -0
- package/dist/ui/panel-needs.js +14 -10
- package/dist/ui/panel-settings.js +82 -17
- package/dist/ui/preview-panel.js +55 -15
- package/dist/ui/preview-status.js +28 -0
- package/dist/ui/preview.js +44 -3
- package/dist/ui/settings-dialogs.js +11 -6
- package/dist/ui/settings-model.js +3 -1
- package/dist/ui/settings-strings.js +32 -12
- package/dist/ui/settings-token.js +8 -1
- package/dist/ui/settings-wizard.js +5 -3
- package/dist/ui/shared/app-i18n.js +41 -15
- package/dist/ui/shared/app-prefs.js +250 -0
- package/dist/ui/shared/app-route.js +6 -1
- package/dist/ui/shared/app-shell.js +165 -8
- package/dist/ui/shared/app-store.js +5 -1
- package/dist/ui/shared/app-stream.js +26 -5
- package/dist/ui/shared/app.css +170 -30
- package/dist/ui/shared/app.js +11 -1
- package/dist/ui/shared/fonts/README.md +1 -1
- package/dist/ui/shared/header-tools.js +83 -13
- package/dist/ui/shared/instance-nav.js +178 -51
- package/dist/ui/shared/instance-switch.js +8 -5
- package/dist/ui/shared/panel-view.js +21 -4
- package/dist/ui/shared/signin.js +15 -9
- package/dist/ui/shared/theme.js +39 -7
- package/dist/ui/shared/tokens.css +1 -1
- package/dist/ui/shared/ui-icons.js +2 -0
- package/dist/ui/shared/usage-dialog.js +23 -5
- package/dist/ui/shared/view-strings.js +40 -4
- package/dist/ui/signin.html +8 -3
- package/dist/unrecognised-prompt.d.ts +23 -0
- package/dist/unrecognised-prompt.js +74 -0
- package/dist/unrecognised-prompt.js.map +1 -0
- package/dist/update-check.d.ts +31 -0
- package/dist/update-check.js +47 -0
- package/dist/update-check.js.map +1 -1
- package/dist/update-install.d.ts +13 -1
- package/dist/update-install.js +16 -4
- package/dist/update-install.js.map +1 -1
- package/dist/update-launch.d.ts +48 -1
- package/dist/update-launch.js +159 -5
- package/dist/update-launch.js.map +1 -1
- package/dist/update-marker.d.ts +51 -0
- package/dist/update-marker.js +61 -0
- package/dist/update-marker.js.map +1 -1
- package/dist/usage/i18n-keys.d.ts +1 -1
- package/dist/usage/i18n-keys.js +1 -1
- package/dist/usage/i18n-keys.js.map +1 -1
- package/dist/usage/providers.d.ts +62 -13
- package/dist/usage/providers.js +256 -68
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage/statusline-usage.d.ts +6 -2
- package/dist/usage/statusline-usage.js +10 -4
- package/dist/usage/statusline-usage.js.map +1 -1
- package/dist/view-api.js +31 -9
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +82 -4
- package/dist/web-api.js +632 -88
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.js +5 -2
- package/dist/web-auth.js.map +1 -1
- package/dist/web-chat-history.d.ts +26 -1
- package/dist/web-chat-history.js +47 -8
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +41 -5
- package/dist/web-commands.js +25 -7
- package/dist/web-commands.js.map +1 -1
- package/dist/web-login.d.ts +14 -0
- package/dist/web-login.js +4 -2
- package/dist/web-login.js.map +1 -1
- package/dist/web-mentions.d.ts +44 -0
- package/dist/web-mentions.js +75 -0
- package/dist/web-mentions.js.map +1 -0
- package/dist/web-prefs.d.ts +81 -0
- package/dist/web-prefs.js +297 -0
- package/dist/web-prefs.js.map +1 -0
- package/dist/web-preview.d.ts +14 -1
- package/dist/web-preview.js +35 -13
- package/dist/web-preview.js.map +1 -1
- package/dist/web-profile.d.ts +32 -0
- package/dist/web-profile.js +132 -0
- package/dist/web-profile.js.map +1 -0
- package/dist/web-reply-files.d.ts +45 -0
- package/dist/web-reply-files.js +223 -0
- package/dist/web-reply-files.js.map +1 -0
- package/dist/web-schedules.d.ts +12 -0
- package/dist/web-schedules.js +34 -0
- package/dist/web-schedules.js.map +1 -0
- package/dist/web-server-drain.d.ts +10 -0
- package/dist/web-server-drain.js +50 -0
- package/dist/web-server-drain.js.map +1 -0
- package/dist/web-session.d.ts +14 -0
- package/dist/web-session.js +11 -1
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +3 -1
- package/dist/web-settings-patch.js +4 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-shell-routes.d.ts +4 -0
- package/dist/web-shell-routes.js +9 -0
- package/dist/web-shell-routes.js.map +1 -1
- package/dist/web-upload.d.ts +135 -7
- package/dist/web-upload.js +315 -51
- package/dist/web-upload.js.map +1 -1
- package/launcher/install-admission.cjs +61 -6
- package/package.json +3 -3
- package/templates/launchd.plist.ejs +3 -1
- package/templates/systemd.service.ejs +2 -1
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #1753: carry a Claude Code conversation across a credential-profile switch.
|
|
3
|
+
*
|
|
4
|
+
* Each profile is its own `CLAUDE_CONFIG_DIR`, and a conversation is a transcript under it:
|
|
5
|
+
* `<config>/projects/<cwd key>/<session id>.jsonl`, with its `<session id>/` folder (subagents, tool results) beside it.
|
|
6
|
+
* The new profile has no conversation for this directory, so a switch used to start fresh with a handover. Copying the
|
|
7
|
+
* session into the new profile and making it the newest there lets the relaunch's `--continue` pick it up — verified on
|
|
8
|
+
* a real account with claude 2.1.296 (#1753).
|
|
9
|
+
*
|
|
10
|
+
* Copy, never move: the old profile keeps its original untouched, so switching back still has it. Everything is written
|
|
11
|
+
* under a temporary name and renamed into place, so claude never sees a half-copied transcript. A failure says why, and
|
|
12
|
+
* the caller then starts fresh with a handover, as before. Up to the rename into place the target is left as it was;
|
|
13
|
+
* the one step after it (dating the copy as the newest) can fail with the copy already there — or, switching back,
|
|
14
|
+
* already in place of the older one — which the fresh start leaves unused.
|
|
15
|
+
*/
|
|
16
|
+
import { constants as fsConstants } from "node:fs";
|
|
17
|
+
import { copyFile, cp, mkdir, open, readdir, rename, rm, stat, statfs, utimes } from "node:fs/promises";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
import { claudeProjectKey } from "./claude-code.js";
|
|
20
|
+
const SESSION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
21
|
+
/** Room kept free beyond the copy itself. */
|
|
22
|
+
const SPACE_MARGIN = 64 * 1024 * 1024;
|
|
23
|
+
/** How much of an existing copy is compared, at its start and at its end, to tell an older copy from another history. */
|
|
24
|
+
const PREFIX_PROBE = 64 * 1024;
|
|
25
|
+
async function defaultFreeBytes(dir) {
|
|
26
|
+
const s = await statfs(dir);
|
|
27
|
+
return Number(s.bavail) * Number(s.bsize);
|
|
28
|
+
}
|
|
29
|
+
async function sizeOf(path) {
|
|
30
|
+
try {
|
|
31
|
+
return (await stat(path)).size;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function treeBytes(dir) {
|
|
38
|
+
let total = 0;
|
|
39
|
+
let entries;
|
|
40
|
+
try {
|
|
41
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
for (const e of entries) {
|
|
47
|
+
const p = join(dir, e.name);
|
|
48
|
+
if (e.isDirectory())
|
|
49
|
+
total += await treeBytes(p);
|
|
50
|
+
else if (e.isFile())
|
|
51
|
+
total += (await sizeOf(p)) ?? 0;
|
|
52
|
+
}
|
|
53
|
+
return total;
|
|
54
|
+
}
|
|
55
|
+
async function readAt(path, offset, length) {
|
|
56
|
+
const fh = await open(path, "r");
|
|
57
|
+
try {
|
|
58
|
+
const buf = Buffer.alloc(length);
|
|
59
|
+
const { bytesRead } = await fh.read(buf, 0, length, offset);
|
|
60
|
+
return buf.subarray(0, bytesRead);
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
await fh.close();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Is `target` an older copy of `source` — the same conversation, cut shorter? (Switching back: A → B → A finds A's
|
|
68
|
+
* original, which B's copy has since grown past.) Its start and its end must match the source at the same offsets.
|
|
69
|
+
*/
|
|
70
|
+
async function isOlderCopy(target, targetSize, source, sourceSize) {
|
|
71
|
+
if (targetSize > sourceSize)
|
|
72
|
+
return false;
|
|
73
|
+
const head = Math.min(PREFIX_PROBE, targetSize);
|
|
74
|
+
const tailAt = Math.max(0, targetSize - PREFIX_PROBE);
|
|
75
|
+
const [th, sh, tt, st] = await Promise.all([
|
|
76
|
+
readAt(target, 0, head), readAt(source, 0, head),
|
|
77
|
+
readAt(target, tailAt, targetSize - tailAt), readAt(source, tailAt, targetSize - tailAt),
|
|
78
|
+
]);
|
|
79
|
+
return th.equals(sh) && tt.equals(st);
|
|
80
|
+
}
|
|
81
|
+
/** The newest mtime (ms) among the other transcripts in the target's project folder. */
|
|
82
|
+
async function newestOtherTranscript(dir, except) {
|
|
83
|
+
let newest = 0;
|
|
84
|
+
try {
|
|
85
|
+
for (const e of await readdir(dir, { withFileTypes: true })) {
|
|
86
|
+
if (!e.isFile() || !e.name.endsWith(".jsonl") || e.name === except)
|
|
87
|
+
continue;
|
|
88
|
+
const m = (await stat(join(dir, e.name))).mtimeMs;
|
|
89
|
+
if (m > newest)
|
|
90
|
+
newest = m;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch { /* no folder yet */ }
|
|
94
|
+
return newest;
|
|
95
|
+
}
|
|
96
|
+
export async function carryClaudeConversation(req) {
|
|
97
|
+
const started = performance.now();
|
|
98
|
+
const sid = (req.sessionId ?? "").trim();
|
|
99
|
+
if (!SESSION_ID.test(sid))
|
|
100
|
+
return { carried: false, reason: "no session id to carry" };
|
|
101
|
+
if (!req.workingDirectory.trim())
|
|
102
|
+
return { carried: false, reason: "no working directory" };
|
|
103
|
+
if (req.fromConfigDir === req.toConfigDir)
|
|
104
|
+
return { carried: false, reason: "same config dir" };
|
|
105
|
+
const key = claudeProjectKey(req.workingDirectory);
|
|
106
|
+
const fromDir = join(req.fromConfigDir, "projects", key);
|
|
107
|
+
const toDir = join(req.toConfigDir, "projects", key);
|
|
108
|
+
const name = `${sid}.jsonl`;
|
|
109
|
+
const source = join(fromDir, name), target = join(toDir, name);
|
|
110
|
+
const sourceSize = await sizeOf(source);
|
|
111
|
+
if (sourceSize === null)
|
|
112
|
+
return { carried: false, reason: "the session's transcript is not in the old profile" };
|
|
113
|
+
const targetSize = await sizeOf(target);
|
|
114
|
+
let replacedOlderCopy = false;
|
|
115
|
+
if (targetSize !== null) {
|
|
116
|
+
if (!await isOlderCopy(target, targetSize, source, sourceSize).catch(() => false)) {
|
|
117
|
+
return { carried: false, reason: "the new profile already holds a different version of this conversation" };
|
|
118
|
+
}
|
|
119
|
+
replacedOlderCopy = true;
|
|
120
|
+
}
|
|
121
|
+
const folderBytes = await treeBytes(join(fromDir, sid));
|
|
122
|
+
const needed = sourceSize + folderBytes + SPACE_MARGIN;
|
|
123
|
+
try {
|
|
124
|
+
await mkdir(toDir, { recursive: true });
|
|
125
|
+
const free = await (req.freeBytes ?? defaultFreeBytes)(toDir);
|
|
126
|
+
if (free < needed)
|
|
127
|
+
return { carried: false, reason: `not enough free space (${needed} bytes needed, ${free} free)` };
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
return { carried: false, reason: `the new profile's folder is not usable: ${err.message}` };
|
|
131
|
+
}
|
|
132
|
+
const tag = `.carry-${process.pid}-${Math.floor(started)}`;
|
|
133
|
+
const tmpFile = join(toDir, `.${name}${tag}`);
|
|
134
|
+
const tmpFolder = join(toDir, `.${sid}${tag}`);
|
|
135
|
+
try {
|
|
136
|
+
// A copy-on-write clone where the filesystem has one (btrfs, xfs, APFS), a plain copy elsewhere.
|
|
137
|
+
await copyFile(source, tmpFile, fsConstants.COPYFILE_FICLONE);
|
|
138
|
+
// The session's folder first, so the transcript never appears before the files it refers to.
|
|
139
|
+
if (await sizeOf(join(fromDir, sid)) !== null) {
|
|
140
|
+
await cp(join(fromDir, sid), tmpFolder, { recursive: true, errorOnExist: false, force: true });
|
|
141
|
+
if (await sizeOf(join(toDir, sid)) === null)
|
|
142
|
+
await rename(tmpFolder, join(toDir, sid));
|
|
143
|
+
else {
|
|
144
|
+
// Switching back: the folder is there already. It takes the copy's files; its own extra files stay.
|
|
145
|
+
await cp(tmpFolder, join(toDir, sid), { recursive: true, errorOnExist: false, force: true });
|
|
146
|
+
await rm(tmpFolder, { recursive: true, force: true });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
await rename(tmpFile, target);
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
await rm(tmpFile, { force: true }).catch(() => undefined);
|
|
153
|
+
await rm(tmpFolder, { recursive: true, force: true }).catch(() => undefined);
|
|
154
|
+
return { carried: false, reason: `copy failed: ${err.message}` };
|
|
155
|
+
}
|
|
156
|
+
// Newest in its folder, so `--continue` picks it — even past another transcript dated ahead of this clock.
|
|
157
|
+
const now = (req.now ?? Date.now)();
|
|
158
|
+
const mtime = Math.max(now, (await newestOtherTranscript(toDir, name)) + 1_000);
|
|
159
|
+
try {
|
|
160
|
+
await utimes(target, mtime / 1000, mtime / 1000);
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
return { carried: false, reason: `could not date the copy as the newest: ${err.message}` };
|
|
164
|
+
}
|
|
165
|
+
// `/rewind` reaches edits from before the switch: the session's file history, merged, never overwriting. Best effort.
|
|
166
|
+
await cp(join(req.fromConfigDir, "file-history", sid), join(req.toConfigDir, "file-history", sid), { recursive: true, errorOnExist: false, force: false })
|
|
167
|
+
.catch(() => undefined);
|
|
168
|
+
return { carried: true, sessionId: sid, bytes: sourceSize + folderBytes, ms: Math.round(performance.now() - started), replacedOlderCopy };
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=claude-carry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-carry.js","sourceRoot":"","sources":["../../src/backend/claude-carry.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
import { type CliBackend, type CliBackendConfig, type ErrorPattern, type InputBox, type RuntimeDialog, type StartupDialog } from "./types.js";
|
|
2
2
|
/** Mirror Claude Code's ~/.claude/projects key for a working directory. Exported for the detection-signal seam. */
|
|
3
3
|
export declare function claudeProjectKey(cwd: string): string;
|
|
4
|
+
/**
|
|
5
|
+
* #1754: the directory Claude Code keys a project's auto-memory by — the repository's own root, which for a git
|
|
6
|
+
* worktree is the main checkout's (its memory sits under `projects/<that root's key>/memory/`, shared by every worktree),
|
|
7
|
+
* and for a directory outside any repository the directory itself. Read from the files git leaves (`.git` as a folder,
|
|
8
|
+
* or a worktree's `.git` file → its `commondir`), never by running git: this runs at an instance's launch.
|
|
9
|
+
*/
|
|
10
|
+
export declare function claudeMemoryRoot(cwd: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* #1754: a named profile is its own `projects/`, so Claude Code's auto-memory for this project would start empty there
|
|
13
|
+
* and stay apart from every other instance's. Memory is knowledge, not an account — shared like the instructions,
|
|
14
|
+
* agents and skills are (#1699 (a)): `<profile>/projects/<repo key>/memory` becomes a link to the shared one
|
|
15
|
+
* (`<shared config>/projects/<repo key>/memory`). Transcripts beside it stay the profile's own.
|
|
16
|
+
* Never replaces anything real: a non-empty memory folder in the profile is left as it is (and said so); an empty one
|
|
17
|
+
* — what claude makes before it has written anything — gives way to the link. Returns what it found or did.
|
|
18
|
+
*/
|
|
19
|
+
export declare function linkSharedClaudeMemory(profileDir: string, workingDirectory: string, sharedConfigDir?: string): "linked" | "already-linked" | "kept-own" | "kept-other-link" | "failed";
|
|
20
|
+
/**
|
|
21
|
+
* #1699: the config dir of a NAMED credential profile — the one string every writer and reader of that profile uses
|
|
22
|
+
* (claude names its macOS Keychain item after it). Null for no profile.
|
|
23
|
+
*/
|
|
24
|
+
export declare function claudeProfileConfigDir(backendOptions: Record<string, unknown> | undefined): string | null;
|
|
25
|
+
/**
|
|
26
|
+
* Claude Code's config dir as an instance's CLI sees it: its profile's (#1699), else exactly as before —
|
|
27
|
+
* `CLAUDE_CONFIG_DIR` from the fleet's own environment, or `~/.claude`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function claudeConfigDirFor(profileDir: string | null | undefined): string;
|
|
30
|
+
/**
|
|
31
|
+
* #1699: the macOS Keychain item holding the login of claude run with `CLAUDE_CONFIG_DIR=<configDir>` — claude
|
|
32
|
+
* 2.1.296's own naming: the exact string it was given, NFC-normalised, hashed (sha256, first 8 hex digits). So the dir
|
|
33
|
+
* must be spelled exactly as at launch (credentialProfileHome: absolute, no trailing slash). Only a named profile has
|
|
34
|
+
* one: the shared login, with the variable unset, is the unsuffixed "Claude Code-credentials".
|
|
35
|
+
*/
|
|
36
|
+
export declare function claudeKeychainService(configDir: string): string;
|
|
4
37
|
/**
|
|
5
38
|
* Take `lockPath` via exclusive create, recording {pid, token} so a stale lock
|
|
6
39
|
* can be attributed. Returns the token to release with, or null when the lock
|
|
@@ -71,6 +104,33 @@ export declare function claudeColdResumePromptState(pane: string): {
|
|
|
71
104
|
* is why the box is found by its frame and never by the glyph. Anything else — no frame near the bottom, a first row
|
|
72
105
|
* that is not the prompt, a row that is not indented — is not a box this reader can vouch for: null.
|
|
73
106
|
*/
|
|
107
|
+
/**
|
|
108
|
+
* #1690: claude 2.1.296's cache-warning confirmation for a runtime `/model` (and, by its binary strings, `/effort`):
|
|
109
|
+
*
|
|
110
|
+
* Switch model? (or: Change effort level?)
|
|
111
|
+
* Your next response will be slower and use more tokens
|
|
112
|
+
* This conversation is cached for the current model. Switching to Opus 5.5 means …
|
|
113
|
+
* ❯ 1. Yes, switch to Opus 5.5
|
|
114
|
+
* 2. No, go back
|
|
115
|
+
*
|
|
116
|
+
* Shown only with a warm cache for another model than the last turn's. The default cursor is Yes; Escape is a clean
|
|
117
|
+
* "No" (`Kept model as …`); a digit selects and confirms. `target` is what claude will switch to: a display name for a
|
|
118
|
+
* name it knows (`opus` → "Opus 5.5"), the raw input for one it does not (it validates nothing). Bottom-anchored: the
|
|
119
|
+
* options are the last rows apart from a short footer, so a transcript quoting the dialog is not it.
|
|
120
|
+
*/
|
|
121
|
+
export interface ClaudeSwitchDialog {
|
|
122
|
+
kind: "model" | "effort";
|
|
123
|
+
target: string;
|
|
124
|
+
cursor: 1 | 2 | null;
|
|
125
|
+
}
|
|
126
|
+
export declare function claudeSwitchDialog(pane: string): ClaudeSwitchDialog | null;
|
|
127
|
+
/**
|
|
128
|
+
* #1690: does the model claude is about to switch to name the one AgEnD asked for? Display-name equality: case, a
|
|
129
|
+
* `claude-` prefix, separators and the 1M marker do not count (`claude-sonnet-5-5` ≡ "Sonnet 5.5"). A bare family alias
|
|
130
|
+
* matches its family's version (`opus` ≡ "Opus 5.5"); a dated id matches without its date. `default` / `opusplan` resolve to names AgEnD cannot predict:
|
|
131
|
+
* any name claude resolved (not the raw input echoed back) is theirs. Anything else is a mismatch.
|
|
132
|
+
*/
|
|
133
|
+
export declare function claudeSwitchTargetMatches(requested: string, shown: string): boolean;
|
|
74
134
|
export declare function readClaudeInputBox(pane: string): InputBox | null;
|
|
75
135
|
export interface ClaudeChoiceDialogState {
|
|
76
136
|
/** The dialog is the live, bottom-most interactive region. */
|
|
@@ -157,6 +217,9 @@ export declare class ClaudeCodeBackend implements CliBackend {
|
|
|
157
217
|
}): number | undefined;
|
|
158
218
|
private binaryPath;
|
|
159
219
|
constructor(instanceDir: string);
|
|
220
|
+
/** #1811: what the last launch left out and why — the Log tab and daemon.log, never a channel. */
|
|
221
|
+
private quietLaunchNotes;
|
|
222
|
+
consumeQuietLaunchNotes(): string[];
|
|
160
223
|
buildCommand(config: CliBackendConfig): string;
|
|
161
224
|
private hasProjectSession;
|
|
162
225
|
writeConfig(config: CliBackendConfig): void;
|
|
@@ -282,10 +345,10 @@ export declare class ClaudeCodeBackend implements CliBackend {
|
|
|
282
345
|
* signal is projects[<cwd>].hasTrustDialogAccepted in claude.json. Also marks
|
|
283
346
|
* onboarding complete for an already-authenticated account (see below).
|
|
284
347
|
*/
|
|
285
|
-
preTrust(workingDirectory: string): void;
|
|
348
|
+
preTrust(workingDirectory: string, config?: CliBackendConfig): void;
|
|
286
349
|
/** Pre-approve ANTHROPIC_API_KEY in claude.json to skip the interactive prompt */
|
|
287
350
|
private preApproveApiKey;
|
|
288
|
-
/** Check if user has an active OAuth session in claude.json */
|
|
351
|
+
/** Check if user has an active OAuth session in claude.json (#1699: the instance's own config dir). */
|
|
289
352
|
private hasOAuthSession;
|
|
290
353
|
private writeStatusLineScript;
|
|
291
354
|
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { EFFORT_CAPABILITIES } from "./effort-metadata.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { flagEffort } from "./launch-effort.js";
|
|
3
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
4
|
+
import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmdirSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
5
|
import { homedir } from "node:os";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import { credentialHomeSpec, credentialProfileHome, prepareCredentialProfileHome, resolveCredentialProfile } from "./credential-profile.js";
|
|
8
|
+
import { getAgendHome } from "../paths.js";
|
|
5
9
|
import { CLI_PROBE_LONGEST_LEAF_MS, resolveBinary, shellQuote, validateModel, warnIfModelMismatch } from "./types.js";
|
|
6
10
|
/** Mirror Claude Code's ~/.claude/projects key for a working directory. Exported for the detection-signal seam. */
|
|
7
11
|
export function claudeProjectKey(cwd) {
|
|
@@ -24,9 +28,127 @@ export function claudeProjectKey(cwd) {
|
|
|
24
28
|
}
|
|
25
29
|
return `${sanitized.slice(0, 200)}-${Math.abs(hash).toString(36)}`;
|
|
26
30
|
}
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
/**
|
|
32
|
+
* #1754: the directory Claude Code keys a project's auto-memory by — the repository's own root, which for a git
|
|
33
|
+
* worktree is the main checkout's (its memory sits under `projects/<that root's key>/memory/`, shared by every worktree),
|
|
34
|
+
* and for a directory outside any repository the directory itself. Read from the files git leaves (`.git` as a folder,
|
|
35
|
+
* or a worktree's `.git` file → its `commondir`), never by running git: this runs at an instance's launch.
|
|
36
|
+
*/
|
|
37
|
+
export function claudeMemoryRoot(cwd) {
|
|
38
|
+
let dir = resolve(cwd);
|
|
39
|
+
try {
|
|
40
|
+
dir = realpathSync(dir);
|
|
41
|
+
}
|
|
42
|
+
catch { /* as given */ }
|
|
43
|
+
for (let at = dir;; at = dirname(at)) {
|
|
44
|
+
const dotGit = join(at, ".git");
|
|
45
|
+
let st;
|
|
46
|
+
try {
|
|
47
|
+
st = statSync(dotGit);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
st = null;
|
|
51
|
+
}
|
|
52
|
+
if (st?.isDirectory())
|
|
53
|
+
return at;
|
|
54
|
+
if (st?.isFile()) {
|
|
55
|
+
try {
|
|
56
|
+
const gitdir = /^gitdir:\s*(.+)$/m.exec(readFileSync(dotGit, "utf-8"))?.[1]?.trim();
|
|
57
|
+
if (gitdir) {
|
|
58
|
+
const worktreeGitDir = resolve(at, gitdir);
|
|
59
|
+
const common = resolve(worktreeGitDir, readFileSync(join(worktreeGitDir, "commondir"), "utf-8").trim());
|
|
60
|
+
if (basename(common) === ".git")
|
|
61
|
+
return dirname(common);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch { /* a submodule or an unusual layout: this directory is its own root */ }
|
|
65
|
+
return at;
|
|
66
|
+
}
|
|
67
|
+
if (dirname(at) === at)
|
|
68
|
+
return dir;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* #1754: a named profile is its own `projects/`, so Claude Code's auto-memory for this project would start empty there
|
|
73
|
+
* and stay apart from every other instance's. Memory is knowledge, not an account — shared like the instructions,
|
|
74
|
+
* agents and skills are (#1699 (a)): `<profile>/projects/<repo key>/memory` becomes a link to the shared one
|
|
75
|
+
* (`<shared config>/projects/<repo key>/memory`). Transcripts beside it stay the profile's own.
|
|
76
|
+
* Never replaces anything real: a non-empty memory folder in the profile is left as it is (and said so); an empty one
|
|
77
|
+
* — what claude makes before it has written anything — gives way to the link. Returns what it found or did.
|
|
78
|
+
*/
|
|
79
|
+
export function linkSharedClaudeMemory(profileDir, workingDirectory, sharedConfigDir = claudeConfigDirFor(null)) {
|
|
80
|
+
const key = claudeProjectKey(claudeMemoryRoot(workingDirectory));
|
|
81
|
+
const shared = join(sharedConfigDir, "projects", key, "memory");
|
|
82
|
+
const target = join(profileDir, "projects", key, "memory");
|
|
83
|
+
try {
|
|
84
|
+
let existing;
|
|
85
|
+
try {
|
|
86
|
+
existing = lstatSync(target);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
existing = null;
|
|
90
|
+
}
|
|
91
|
+
if (existing?.isSymbolicLink()) {
|
|
92
|
+
if (resolve(dirname(target), readlinkSync(target)) !== resolve(shared))
|
|
93
|
+
return "kept-other-link";
|
|
94
|
+
// Ours — but it may point at nothing yet (linked before the shared side was made, or that side since removed):
|
|
95
|
+
// the shared folder must be there for a note to land. A file in its place throws (EEXIST): the failure path below.
|
|
96
|
+
mkdirSync(shared, { recursive: true });
|
|
97
|
+
return "already-linked";
|
|
98
|
+
}
|
|
99
|
+
if (existing?.isDirectory()) {
|
|
100
|
+
if (readdirSync(target).length > 0) {
|
|
101
|
+
console.warn(`[agend] a credential profile has its own Claude memory, so it is not linked to the shared one: ${target}`);
|
|
102
|
+
return "kept-own";
|
|
103
|
+
}
|
|
104
|
+
rmdirSync(target);
|
|
105
|
+
}
|
|
106
|
+
else if (existing) {
|
|
107
|
+
return "kept-own";
|
|
108
|
+
}
|
|
109
|
+
mkdirSync(shared, { recursive: true });
|
|
110
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
111
|
+
symlinkSync(shared, target, "dir");
|
|
112
|
+
return "linked";
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
console.warn(`[agend] could not link a credential profile's Claude memory to the shared one (${err.message}): ${target}`);
|
|
116
|
+
return "failed";
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* #1699: the config dir of a NAMED credential profile — the one string every writer and reader of that profile uses
|
|
121
|
+
* (claude names its macOS Keychain item after it). Null for no profile.
|
|
122
|
+
*/
|
|
123
|
+
export function claudeProfileConfigDir(backendOptions) {
|
|
124
|
+
let profile;
|
|
125
|
+
try {
|
|
126
|
+
profile = resolveCredentialProfile(backendOptions);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
return profile ? credentialProfileHome(getAgendHome(), "claude-code", profile) : null;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Claude Code's config dir as an instance's CLI sees it: its profile's (#1699), else exactly as before —
|
|
135
|
+
* `CLAUDE_CONFIG_DIR` from the fleet's own environment, or `~/.claude`.
|
|
136
|
+
*/
|
|
137
|
+
export function claudeConfigDirFor(profileDir) {
|
|
138
|
+
return profileDir || process.env.CLAUDE_CONFIG_DIR?.trim() || join(homedir(), ".claude");
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* #1699: the macOS Keychain item holding the login of claude run with `CLAUDE_CONFIG_DIR=<configDir>` — claude
|
|
142
|
+
* 2.1.296's own naming: the exact string it was given, NFC-normalised, hashed (sha256, first 8 hex digits). So the dir
|
|
143
|
+
* must be spelled exactly as at launch (credentialProfileHome: absolute, no trailing slash). Only a named profile has
|
|
144
|
+
* one: the shared login, with the variable unset, is the unsuffixed "Claude Code-credentials".
|
|
145
|
+
*/
|
|
146
|
+
export function claudeKeychainService(configDir) {
|
|
147
|
+
return `Claude Code-credentials-${createHash("sha256").update(configDir.normalize("NFC")).digest("hex").slice(0, 8)}`;
|
|
148
|
+
}
|
|
149
|
+
/** Claude Code's top-level config file: `<profile dir>/.claude.json` for a profile, else honoring CLAUDE_CONFIG_DIR. */
|
|
150
|
+
function claudeJsonPath(profileDir) {
|
|
151
|
+
const configDir = profileDir || process.env.CLAUDE_CONFIG_DIR?.trim();
|
|
30
152
|
return configDir ? join(configDir, ".claude.json") : join(homedir(), ".claude.json");
|
|
31
153
|
}
|
|
32
154
|
function sleepSync(ms) {
|
|
@@ -99,8 +221,8 @@ export function releaseClaudeJsonLock(lockPath, token) {
|
|
|
99
221
|
* unparseable file is likewise left untouched — never clobber the user's whole
|
|
100
222
|
* config over a parse error. Both give up to the getStartupDialogs() fallback.
|
|
101
223
|
*/
|
|
102
|
-
function updateClaudeJson(mutate) {
|
|
103
|
-
const path = claudeJsonPath();
|
|
224
|
+
function updateClaudeJson(mutate, profileDir) {
|
|
225
|
+
const path = claudeJsonPath(profileDir);
|
|
104
226
|
// The parent must exist before both the lock create and the rename — on a
|
|
105
227
|
// fresh machine (no ~/.claude.json yet) an ENOENT here previously made every
|
|
106
228
|
// lock attempt fail, silently degrading to unlocked last-write-wins.
|
|
@@ -308,23 +430,61 @@ const CLAUDE_STATUS_TIP_ROW = /^ {2}⎿/;
|
|
|
308
430
|
const CLAUDE_STATUS_SPINNER_ROW = /^[^\s❯●⎿]/;
|
|
309
431
|
/** A paste Claude shows collapsed in the box; the pasted text itself is not on screen. */
|
|
310
432
|
const CLAUDE_COLLAPSED_PASTE = /\[Pasted text #\d+(?: \+\d+ lines?)?\]/g;
|
|
433
|
+
const SWITCH_TITLE = /^[ \t]*(Switch model\?|Change effort level\?)[ \t]*$/;
|
|
434
|
+
const SWITCH_YES = /^[ \t]*(❯)?[ \t]*1\.[ \t]+Yes, switch to (.+?)[ \t]*$/;
|
|
435
|
+
const SWITCH_NO = /^[ \t]*(❯)?[ \t]*2\.[ \t]+No, go back[ \t]*$/;
|
|
436
|
+
export function claudeSwitchDialog(pane) {
|
|
437
|
+
const rows = pane.replace(/\r/g, "").split("\n").map(r => r.trimEnd());
|
|
438
|
+
let title = -1;
|
|
439
|
+
for (let i = rows.length - 1; i >= 0; i--)
|
|
440
|
+
if (SWITCH_TITLE.test(rows[i])) {
|
|
441
|
+
title = i;
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
if (title < 0)
|
|
445
|
+
return null;
|
|
446
|
+
let yes = -1, no = -1;
|
|
447
|
+
for (let i = title + 1; i < rows.length && i <= title + 12; i++) {
|
|
448
|
+
if (yes < 0 && SWITCH_YES.test(rows[i]))
|
|
449
|
+
yes = i;
|
|
450
|
+
else if (yes >= 0 && SWITCH_NO.test(rows[i])) {
|
|
451
|
+
no = i;
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
if (yes < 0 || no !== yes + 1)
|
|
456
|
+
return null;
|
|
457
|
+
if (rows.slice(no + 1).filter(r => r.trim() !== "").length > 3)
|
|
458
|
+
return null;
|
|
459
|
+
const y = SWITCH_YES.exec(rows[yes]), n = SWITCH_NO.exec(rows[no]);
|
|
460
|
+
const cursor = y[1] && !n[1] ? 1 : n[1] && !y[1] ? 2 : null;
|
|
461
|
+
return { kind: SWITCH_TITLE.exec(rows[title])[1].startsWith("Switch") ? "model" : "effort", target: y[2], cursor };
|
|
462
|
+
}
|
|
311
463
|
/**
|
|
312
|
-
*
|
|
313
|
-
* (
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
* ────────────────────────────────── ← the box's upper rule
|
|
317
|
-
* ❯ [Pasted text #1 +11 lines] ← a long paste stays collapsed until it is submitted
|
|
318
|
-
* ────────────────────────────────── ← lower rule
|
|
319
|
-
* ok ← statusLine
|
|
320
|
-
* ⏵⏵ bypass permissions on (shift+tab to cycle)
|
|
321
|
-
*
|
|
322
|
-
* Bottom-anchored: the lower rule is the last `─` row, with at most CLAUDE_BOX_FOOTER_MAX_ROWS non-blank rows under it;
|
|
323
|
-
* the upper rule is the previous `─` row. The first row inside starts with `❯` (an empty box is a bare `❯`), further
|
|
324
|
-
* rows are indented two columns. Transcript echoes above the box also start with `❯` (with an ordinary space), which
|
|
325
|
-
* is why the box is found by its frame and never by the glyph. Anything else — no frame near the bottom, a first row
|
|
326
|
-
* that is not the prompt, a row that is not indented — is not a box this reader can vouch for: null.
|
|
464
|
+
* #1690: does the model claude is about to switch to name the one AgEnD asked for? Display-name equality: case, a
|
|
465
|
+
* `claude-` prefix, separators and the 1M marker do not count (`claude-sonnet-5-5` ≡ "Sonnet 5.5"). A bare family alias
|
|
466
|
+
* matches its family's version (`opus` ≡ "Opus 5.5"); a dated id matches without its date. `default` / `opusplan` resolve to names AgEnD cannot predict:
|
|
467
|
+
* any name claude resolved (not the raw input echoed back) is theirs. Anything else is a mismatch.
|
|
327
468
|
*/
|
|
469
|
+
export function claudeSwitchTargetMatches(requested, shown) {
|
|
470
|
+
// A dated catalog id names the same model as its display name: claude-sonnet-4-5-20250929 ≡ "Sonnet 4.5" (live, 2.1.296).
|
|
471
|
+
const norm = (s) => s.toLowerCase().replace(/\(1m context\)|1m context|\[1m\]/g, " 1m").replace(/^claude[\s-]+/, "")
|
|
472
|
+
.replace(/[-_]\d{8}(?=$|\s)/g, "").replace(/[\s._-]+/g, "");
|
|
473
|
+
const want = norm(requested), got = norm(shown);
|
|
474
|
+
if (!want || !got)
|
|
475
|
+
return false;
|
|
476
|
+
if (want === "default" || want === "opusplan")
|
|
477
|
+
return shown.trim().toLowerCase() !== requested.trim().toLowerCase();
|
|
478
|
+
if (want === got)
|
|
479
|
+
return true;
|
|
480
|
+
const family = /^(opus|sonnet|haiku|fable)(1m)?$/.exec(want);
|
|
481
|
+
if (family) {
|
|
482
|
+
const m = /^(opus|sonnet|haiku|fable)(\d+)(1m)?$/.exec(got);
|
|
483
|
+
return !!m && m[1] === family[1] && (m[3] ?? "") === (family[2] ?? "");
|
|
484
|
+
}
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
const switchDialogActive = (pane) => claudeSwitchDialog(pane) !== null;
|
|
328
488
|
export function readClaudeInputBox(pane) {
|
|
329
489
|
const rows = claudeRows(pane).map(row => row.trimEnd());
|
|
330
490
|
let bottom = -1;
|
|
@@ -804,13 +964,27 @@ export class ClaudeCodeBackend {
|
|
|
804
964
|
this.instanceDir = instanceDir;
|
|
805
965
|
this.binaryPath = resolveBinary("claude");
|
|
806
966
|
}
|
|
967
|
+
/** #1811: what the last launch left out and why — the Log tab and daemon.log, never a channel. */
|
|
968
|
+
quietLaunchNotes = [];
|
|
969
|
+
consumeQuietLaunchNotes() { const notes = this.quietLaunchNotes; this.quietLaunchNotes = []; return notes; }
|
|
807
970
|
buildCommand(config) {
|
|
808
971
|
const settingsPath = join(this.instanceDir, "claude-settings.json");
|
|
809
972
|
const mcpConfigPath = join(this.instanceDir, "mcp-config.json");
|
|
810
973
|
const envPrefix = ["CMUX_CLAUDE_HOOKS_DISABLED=1", "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1"];
|
|
974
|
+
// #1699: a named credential profile is its own config dir — its login, account, trust and conversations. No profile:
|
|
975
|
+
// nothing added, nothing created (the command is exactly what it was).
|
|
976
|
+
const profileDir = claudeProfileConfigDir(config.backendOptions);
|
|
977
|
+
if (profileDir) {
|
|
978
|
+
prepareCredentialProfileHome(credentialHomeSpec("claude-code"), profileDir);
|
|
979
|
+
// #1754: the project's auto-memory is the shared one, as on the default login.
|
|
980
|
+
linkSharedClaudeMemory(profileDir, config.workingDirectory, credentialHomeSpec("claude-code").sharedRoot());
|
|
981
|
+
envPrefix.push(`CLAUDE_CONFIG_DIR=${shellQuote(profileDir)}`);
|
|
982
|
+
}
|
|
811
983
|
if (process.env.ANTHROPIC_BASE_URL)
|
|
812
984
|
envPrefix.push(`ANTHROPIC_BASE_URL=${shellQuote(process.env.ANTHROPIC_BASE_URL)}`);
|
|
813
|
-
|
|
985
|
+
// The fleet's API key only where this instance's own config dir has no OAuth login: a key in the environment wins
|
|
986
|
+
// over the login, so reading another dir here bills the wrong account (#1699).
|
|
987
|
+
if (process.env.ANTHROPIC_API_KEY && !this.hasOAuthSession(profileDir)) {
|
|
814
988
|
envPrefix.push(`ANTHROPIC_API_KEY=${shellQuote(process.env.ANTHROPIC_API_KEY)}`);
|
|
815
989
|
}
|
|
816
990
|
let cmd = `${envPrefix.join(" ")} ${this.binaryPath} --settings ${settingsPath} --mcp-config ${mcpConfigPath}`;
|
|
@@ -824,7 +998,7 @@ export class ClaudeCodeBackend {
|
|
|
824
998
|
// own project transcript as the source of truth instead of the generic
|
|
825
999
|
// marker alone. This also makes an empty/missing Classic workspace start
|
|
826
1000
|
// fresh rather than entering a resume crash loop.
|
|
827
|
-
if (!config.skipResume && existsSync(sessionIdFile) && this.hasProjectSession(config.workingDirectory)) {
|
|
1001
|
+
if (!config.skipResume && existsSync(sessionIdFile) && this.hasProjectSession(config.workingDirectory, profileDir)) {
|
|
828
1002
|
cmd += " --continue";
|
|
829
1003
|
}
|
|
830
1004
|
if (config.model) {
|
|
@@ -832,6 +1006,11 @@ export class ClaudeCodeBackend {
|
|
|
832
1006
|
warnIfModelMismatch("claude-code", model);
|
|
833
1007
|
cmd += ` --model ${shellQuote(model)}`;
|
|
834
1008
|
}
|
|
1009
|
+
// #1811: the configured effort, where this claude takes it (launch-effort.ts); else a quiet note.
|
|
1010
|
+
const effort = flagEffort("claude-code", config.effort, this.binaryPath);
|
|
1011
|
+
if (effort.arg)
|
|
1012
|
+
cmd += effort.arg;
|
|
1013
|
+
this.quietLaunchNotes = effort.note ? [effort.note] : [];
|
|
835
1014
|
// Additive system prompt: append fleet instructions without overriding Claude's built-in prompt
|
|
836
1015
|
const instrFile = join(this.instanceDir, "fleet-instructions.md");
|
|
837
1016
|
if (existsSync(instrFile)) {
|
|
@@ -839,11 +1018,11 @@ export class ClaudeCodeBackend {
|
|
|
839
1018
|
}
|
|
840
1019
|
return cmd;
|
|
841
1020
|
}
|
|
842
|
-
hasProjectSession(workingDirectory) {
|
|
1021
|
+
hasProjectSession(workingDirectory, profileDir) {
|
|
843
1022
|
const cwd = workingDirectory.trim();
|
|
844
1023
|
if (!cwd)
|
|
845
1024
|
return false;
|
|
846
|
-
const configDir =
|
|
1025
|
+
const configDir = claudeConfigDirFor(profileDir);
|
|
847
1026
|
const encodedCwd = claudeProjectKey(cwd);
|
|
848
1027
|
try {
|
|
849
1028
|
return readdirSync(join(configDir, "projects", encodedCwd), { withFileTypes: true })
|
|
@@ -1276,6 +1455,17 @@ export class ClaudeCodeBackend {
|
|
|
1276
1455
|
{ pattern: /Choose the text style that looks best with your terminal/, isActive: claudeThemePickerActive, keys: ["Enter"], description: "Claude onboarding theme picker — accept the highlighted theme", blocksDelivery: true, verifyAfterKeys: true, autoResolutionKey: "claude-onboarding-theme" },
|
|
1277
1456
|
{ pattern: /Press Enter to continue/, isActive: claudeSecurityNotesActive, keys: ["Enter"], description: "Claude onboarding security notes — continue", blocksDelivery: true, verifyAfterKeys: true, autoResolutionKey: "claude-onboarding-security" },
|
|
1278
1457
|
{ pattern: /Use Claude Code's terminal setup\?/, isActive: claudeTerminalSetupActive, keys: ["Escape"], description: "Claude onboarding terminal-setup offer — skip", blocksDelivery: true, verifyAfterKeys: true, autoResolutionKey: "claude-onboarding-terminal-setup" },
|
|
1458
|
+
// #1690: the model/effort switch confirmation. Held for every delivery while it is up (its default cursor is Yes:
|
|
1459
|
+
// a stray Enter would confirm it) and never answered by the scanner — AgEnD's own /model answers it on a verified
|
|
1460
|
+
// cursor (Daemon.answerOwnSwitchDialog); one AgEnD did not ask for is parked for a human.
|
|
1461
|
+
{
|
|
1462
|
+
pattern: /Switch model\?|Change effort level\?/,
|
|
1463
|
+
isActive: switchDialogActive,
|
|
1464
|
+
keys: [],
|
|
1465
|
+
holdOnly: true,
|
|
1466
|
+
blocksDelivery: true,
|
|
1467
|
+
description: "Claude model/effort switch confirmation (\u201cSwitch model?\u201d) — not asked for by AgEnD, or its answer did not take",
|
|
1468
|
+
},
|
|
1279
1469
|
];
|
|
1280
1470
|
}
|
|
1281
1471
|
getQuitCommand() { return "/exit"; }
|
|
@@ -1369,10 +1559,12 @@ export class ClaudeCodeBackend {
|
|
|
1369
1559
|
* signal is projects[<cwd>].hasTrustDialogAccepted in claude.json. Also marks
|
|
1370
1560
|
* onboarding complete for an already-authenticated account (see below).
|
|
1371
1561
|
*/
|
|
1372
|
-
preTrust(workingDirectory) {
|
|
1562
|
+
preTrust(workingDirectory, config) {
|
|
1373
1563
|
const cwd = workingDirectory?.trim();
|
|
1374
1564
|
if (!cwd)
|
|
1375
1565
|
return;
|
|
1566
|
+
// #1699: trust and onboarding are per config dir — a profile's own `.claude.json`, never the shared one.
|
|
1567
|
+
const profileDir = claudeProfileConfigDir(config?.backendOptions);
|
|
1376
1568
|
const literal = resolve(cwd);
|
|
1377
1569
|
let canonical = literal;
|
|
1378
1570
|
try {
|
|
@@ -1416,15 +1608,16 @@ export class ClaudeCodeBackend {
|
|
|
1416
1608
|
changed = true;
|
|
1417
1609
|
}
|
|
1418
1610
|
return changed;
|
|
1419
|
-
});
|
|
1611
|
+
}, profileDir);
|
|
1420
1612
|
}
|
|
1421
1613
|
catch { /* best effort — startup dialog auto-dismiss remains the fallback */ }
|
|
1422
1614
|
}
|
|
1423
1615
|
/** Pre-approve ANTHROPIC_API_KEY in claude.json to skip the interactive prompt */
|
|
1424
|
-
preApproveApiKey(
|
|
1616
|
+
preApproveApiKey(config) {
|
|
1425
1617
|
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
1426
1618
|
if (!apiKey)
|
|
1427
1619
|
return;
|
|
1620
|
+
const profileDir = claudeProfileConfigDir(config.backendOptions);
|
|
1428
1621
|
const fingerprint = apiKey.length > 20 ? apiKey.slice(-20) : apiKey;
|
|
1429
1622
|
try {
|
|
1430
1623
|
updateClaudeJson(cfg => {
|
|
@@ -1437,14 +1630,14 @@ export class ClaudeCodeBackend {
|
|
|
1437
1630
|
rejected: existing?.rejected ?? [],
|
|
1438
1631
|
};
|
|
1439
1632
|
return true;
|
|
1440
|
-
});
|
|
1633
|
+
}, profileDir);
|
|
1441
1634
|
}
|
|
1442
1635
|
catch { /* best effort — the startup prompt remains the fallback */ }
|
|
1443
1636
|
}
|
|
1444
|
-
/** Check if user has an active OAuth session in claude.json */
|
|
1445
|
-
hasOAuthSession() {
|
|
1637
|
+
/** Check if user has an active OAuth session in claude.json (#1699: the instance's own config dir). */
|
|
1638
|
+
hasOAuthSession(profileDir) {
|
|
1446
1639
|
try {
|
|
1447
|
-
const cfg = JSON.parse(readFileSync(claudeJsonPath(), "utf-8"));
|
|
1640
|
+
const cfg = JSON.parse(readFileSync(claudeJsonPath(profileDir), "utf-8"));
|
|
1448
1641
|
return !!cfg.oauthAccount?.accountUuid;
|
|
1449
1642
|
}
|
|
1450
1643
|
catch {
|