aicodeman 1.48.0 → 1.49.0
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 +12 -3
- package/dist/config/cli-registry/schema.d.ts +1 -0
- package/dist/config/cli-registry/schema.d.ts.map +1 -1
- package/dist/config/cli-registry/schema.js +5 -1
- package/dist/config/cli-registry/schema.js.map +1 -1
- package/dist/config/cli-registry/stock.d.ts.map +1 -1
- package/dist/config/cli-registry/stock.js +71 -5
- package/dist/config/cli-registry/stock.js.map +1 -1
- package/dist/config/cli-registry/types.d.ts +4 -4
- package/dist/config/cli-registry/types.d.ts.map +1 -1
- package/dist/config/foreign-tmux.d.ts +48 -0
- package/dist/config/foreign-tmux.d.ts.map +1 -0
- package/dist/config/foreign-tmux.js +48 -0
- package/dist/config/foreign-tmux.js.map +1 -0
- package/dist/custom-model-injection-apply.d.ts +4 -2
- package/dist/custom-model-injection-apply.d.ts.map +1 -1
- package/dist/custom-model-injection-apply.js +35 -21
- package/dist/custom-model-injection-apply.js.map +1 -1
- package/dist/docker-hosts.d.ts +8 -1
- package/dist/docker-hosts.d.ts.map +1 -1
- package/dist/docker-hosts.js +5 -5
- package/dist/docker-hosts.js.map +1 -1
- package/dist/foreign-tmux-discovery.d.ts +140 -0
- package/dist/foreign-tmux-discovery.d.ts.map +1 -0
- package/dist/foreign-tmux-discovery.js +366 -0
- package/dist/foreign-tmux-discovery.js.map +1 -0
- package/dist/foreign-tmux.d.ts +381 -0
- package/dist/foreign-tmux.d.ts.map +1 -0
- package/dist/foreign-tmux.js +775 -0
- package/dist/foreign-tmux.js.map +1 -0
- package/dist/git-fetch-scheduler.d.ts +2 -0
- package/dist/git-fetch-scheduler.d.ts.map +1 -1
- package/dist/git-fetch-scheduler.js +1 -1
- package/dist/git-fetch-scheduler.js.map +1 -1
- package/dist/git-fetch.d.ts +1 -1
- package/dist/git-fetch.d.ts.map +1 -1
- package/dist/git-workspace-status.d.ts +2 -2
- package/dist/git-workspace-status.d.ts.map +1 -1
- package/dist/hooks-config.d.ts +2 -1
- package/dist/hooks-config.d.ts.map +1 -1
- package/dist/hooks-config.js +2 -1
- package/dist/hooks-config.js.map +1 -1
- package/dist/live-worktree.d.ts +53 -0
- package/dist/live-worktree.d.ts.map +1 -0
- package/dist/live-worktree.js +97 -0
- package/dist/live-worktree.js.map +1 -0
- package/dist/mux-interface.d.ts +12 -1
- package/dist/mux-interface.d.ts.map +1 -1
- package/dist/reboot-restore.d.ts +1 -1
- package/dist/reboot-restore.d.ts.map +1 -1
- package/dist/reboot-restore.js +9 -0
- package/dist/reboot-restore.js.map +1 -1
- package/dist/remote-files.d.ts +150 -5
- package/dist/remote-files.d.ts.map +1 -1
- package/dist/remote-files.js +505 -7
- package/dist/remote-files.js.map +1 -1
- package/dist/remote-hosts.d.ts +15 -9
- package/dist/remote-hosts.d.ts.map +1 -1
- package/dist/remote-hosts.js +42 -20
- package/dist/remote-hosts.js.map +1 -1
- package/dist/session-cli-registry-bridge.d.ts.map +1 -1
- package/dist/session-cli-registry-bridge.js +19 -8
- package/dist/session-cli-registry-bridge.js.map +1 -1
- package/dist/session-env-clamp.d.ts +1 -1
- package/dist/session-env-clamp.d.ts.map +1 -1
- package/dist/session-env-clamp.js +4 -3
- package/dist/session-env-clamp.js.map +1 -1
- package/dist/session.d.ts +91 -21
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +190 -33
- package/dist/session.js.map +1 -1
- package/dist/tmux-manager.d.ts +74 -12
- package/dist/tmux-manager.d.ts.map +1 -1
- package/dist/tmux-manager.js +256 -41
- package/dist/tmux-manager.js.map +1 -1
- package/dist/types/foreign-tmux.d.ts +101 -0
- package/dist/types/foreign-tmux.d.ts.map +1 -0
- package/dist/types/foreign-tmux.js +27 -0
- package/dist/types/foreign-tmux.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/session.d.ts +106 -2
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/session.js.map +1 -1
- package/dist/types/update.d.ts +4 -1
- package/dist/types/update.d.ts.map +1 -1
- package/dist/web/edit-history-sync.d.ts +27 -7
- package/dist/web/edit-history-sync.d.ts.map +1 -1
- package/dist/web/edit-history-sync.js +35 -19
- package/dist/web/edit-history-sync.js.map +1 -1
- package/dist/web/launch-defaults.d.ts +1 -1
- package/dist/web/launch-defaults.js +2 -2
- package/dist/web/launch-defaults.js.map +1 -1
- package/dist/web/linked-cases-writer.d.ts +43 -0
- package/dist/web/linked-cases-writer.d.ts.map +1 -0
- package/dist/web/linked-cases-writer.js +107 -0
- package/dist/web/linked-cases-writer.js.map +1 -0
- package/dist/web/paste-image-gc.d.ts +4 -1
- package/dist/web/paste-image-gc.d.ts.map +1 -1
- package/dist/web/paste-image-gc.js +4 -2
- package/dist/web/paste-image-gc.js.map +1 -1
- package/dist/web/public/admin-ui.js.gz +0 -0
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/{app.963b5ba8.js → app.9ea37070.js} +6 -6
- package/dist/web/public/app.9ea37070.js.br +0 -0
- package/dist/web/public/app.9ea37070.js.gz +0 -0
- package/dist/web/public/approvals-ui.js.gz +0 -0
- package/dist/web/public/constants.782170e1.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/edit-history-ui.js +27 -14
- package/dist/web/public/edit-history-ui.js.br +0 -0
- package/dist/web/public/edit-history-ui.js.gz +0 -0
- package/dist/web/public/entrance-animations.js.gz +0 -0
- package/dist/web/public/foreign-sessions.js +476 -0
- package/dist/web/public/foreign-sessions.js.br +0 -0
- package/dist/web/public/foreign-sessions.js.gz +0 -0
- package/dist/web/public/git-status-ui.js +5 -3
- package/dist/web/public/git-status-ui.js.br +0 -0
- package/dist/web/public/git-status-ui.js.gz +0 -0
- package/dist/web/public/header-divider.eb2d2463.js.gz +0 -0
- package/dist/web/public/home-sessions.js.gz +0 -0
- package/dist/web/public/host-wake-ui.js.gz +0 -0
- package/dist/web/public/i18n.a5e82c95.js +2 -0
- package/dist/web/public/i18n.a5e82c95.js.br +0 -0
- package/dist/web/public/i18n.a5e82c95.js.gz +0 -0
- package/dist/web/public/image-input.a5d0f8f3.js.gz +0 -0
- package/dist/web/public/index.html +36 -17
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.8bc46081.js.gz +0 -0
- package/dist/web/public/{keyboard-accessory.d4ec5957.js → keyboard-accessory.b941676c.js} +3 -1
- package/dist/web/public/keyboard-accessory.b941676c.js.br +0 -0
- package/dist/web/public/{keyboard-accessory.d4ec5957.js.gz → keyboard-accessory.b941676c.js.gz} +0 -0
- package/dist/web/public/mobile-handlers.f7c3f692.js.gz +0 -0
- package/dist/web/public/mobile-ime-preview.2dbaf262.js.gz +0 -0
- package/dist/web/public/mobile-overview.js +10 -0
- package/dist/web/public/mobile-overview.js.br +0 -0
- package/dist/web/public/mobile-overview.js.gz +0 -0
- package/dist/web/public/{mobile.5ef2ce85.css → mobile.67c8eb5b.css} +1 -1
- package/dist/web/public/mobile.67c8eb5b.css.br +0 -0
- package/dist/web/public/{mobile.5ef2ce85.css.gz → mobile.67c8eb5b.css.gz} +0 -0
- package/dist/web/public/notification-manager.feb82c04.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/{panels-ui.b100727d.js → panels-ui.8bcd4473.js} +4 -4
- package/dist/web/public/panels-ui.8bcd4473.js.br +0 -0
- package/dist/web/public/panels-ui.8bcd4473.js.gz +0 -0
- package/dist/web/public/ralph-panel.b350e2ef.js.gz +0 -0
- package/dist/web/public/ralph-wizard.b6c0dd19.js.gz +0 -0
- package/dist/web/public/readmymind-ui.js.gz +0 -0
- package/dist/web/public/reboot-restore-ui.js.gz +0 -0
- package/dist/web/public/respawn-ui.304ba0d4.js.gz +0 -0
- package/dist/web/public/sanitize-html.e7147e76.js.gz +0 -0
- package/dist/web/public/session-lineage.js.gz +0 -0
- package/dist/web/public/{session-ui.5ac82f2c.js → session-ui.a7c8c095.js} +11 -11
- package/dist/web/public/session-ui.a7c8c095.js.br +0 -0
- package/dist/web/public/session-ui.a7c8c095.js.gz +0 -0
- package/dist/web/public/{settings-ui.d1d4fb99.js → settings-ui.18f92052.js} +3 -3
- package/dist/web/public/settings-ui.18f92052.js.br +0 -0
- package/dist/web/public/settings-ui.18f92052.js.gz +0 -0
- package/dist/web/public/spreadsheet-preview-worker.js.gz +0 -0
- package/dist/web/public/spreadsheet-preview.75c751de.js.gz +0 -0
- package/dist/web/public/spreadsheet-xlsx-core.js.gz +0 -0
- package/dist/web/public/{styles.77dc538f.css → styles.6a718331.css} +1 -1
- package/dist/web/public/styles.6a718331.css.br +0 -0
- package/dist/web/public/{styles.77dc538f.css.gz → styles.6a718331.css.gz} +0 -0
- package/dist/web/public/subagent-windows.07277a58.js.gz +0 -0
- package/dist/web/public/sw.js +2 -2
- package/dist/web/public/sw.js.br +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/tab-layout-browser.2d6189f7.js.gz +0 -0
- package/dist/web/public/tab-rail-resize.b5cdceaf.js.gz +0 -0
- package/dist/web/public/terminal-keycode229-recovery.98843069.js.gz +0 -0
- package/dist/web/public/terminal-split.js.gz +0 -0
- package/dist/web/public/terminal-tile.js +2 -2
- package/dist/web/public/terminal-tile.js.br +0 -0
- package/dist/web/public/terminal-tile.js.gz +0 -0
- package/dist/web/public/{terminal-ui.d7c75ce8.js → terminal-ui.a7883640.js} +2 -2
- package/dist/web/public/terminal-ui.a7883640.js.br +0 -0
- package/dist/web/public/terminal-ui.a7883640.js.gz +0 -0
- package/dist/web/public/tile-grid.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/exceljs.min.js.gz +0 -0
- package/dist/web/public/vendor/fflate.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-predictive-echo.276b11d9.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.1be42307.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.2342c37b.js.gz +0 -0
- package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/public/whats-new.json +9 -9
- package/dist/web/routes/case-routes.d.ts.map +1 -1
- package/dist/web/routes/case-routes.js +72 -53
- package/dist/web/routes/case-routes.js.map +1 -1
- package/dist/web/routes/discord-routes.js +1 -1
- package/dist/web/routes/discord-routes.js.map +1 -1
- package/dist/web/routes/edit-history-routes.d.ts +2 -2
- package/dist/web/routes/edit-history-routes.d.ts.map +1 -1
- package/dist/web/routes/edit-history-routes.js +4 -3
- package/dist/web/routes/edit-history-routes.js.map +1 -1
- package/dist/web/routes/file-routes.d.ts.map +1 -1
- package/dist/web/routes/file-routes.js +213 -102
- package/dist/web/routes/file-routes.js.map +1 -1
- package/dist/web/routes/git-status-routes.d.ts.map +1 -1
- package/dist/web/routes/git-status-routes.js +7 -2
- package/dist/web/routes/git-status-routes.js.map +1 -1
- package/dist/web/routes/mux-routes.d.ts +10 -3
- package/dist/web/routes/mux-routes.d.ts.map +1 -1
- package/dist/web/routes/mux-routes.js +59 -1
- package/dist/web/routes/mux-routes.js.map +1 -1
- package/dist/web/routes/ralph-routes.d.ts.map +1 -1
- package/dist/web/routes/ralph-routes.js +22 -0
- package/dist/web/routes/ralph-routes.js.map +1 -1
- package/dist/web/routes/readmymind-routes.d.ts.map +1 -1
- package/dist/web/routes/readmymind-routes.js +3 -2
- package/dist/web/routes/readmymind-routes.js.map +1 -1
- package/dist/web/routes/respawn-routes.d.ts.map +1 -1
- package/dist/web/routes/respawn-routes.js +24 -0
- package/dist/web/routes/respawn-routes.js.map +1 -1
- package/dist/web/routes/session-routes.d.ts +11 -8
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +262 -29
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/routes/system-routes.d.ts.map +1 -1
- package/dist/web/routes/system-routes.js +3 -2
- package/dist/web/routes/system-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +24 -1
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +34 -9
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/self-update.d.ts +9 -0
- package/dist/web/self-update.d.ts.map +1 -1
- package/dist/web/self-update.js +19 -3
- package/dist/web/self-update.js.map +1 -1
- package/dist/web/server.d.ts +16 -3
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +84 -24
- package/dist/web/server.js.map +1 -1
- package/dist/web/session-wait-registry.d.ts +13 -0
- package/dist/web/session-wait-registry.d.ts.map +1 -1
- package/dist/web/session-wait-registry.js +16 -8
- package/dist/web/session-wait-registry.js.map +1 -1
- package/package.json +2 -2
- package/skills/codeman/SKILL.md +20 -4
- package/dist/web/public/app.963b5ba8.js.br +0 -0
- package/dist/web/public/app.963b5ba8.js.gz +0 -0
- package/dist/web/public/i18n.33f930d7.js +0 -2
- package/dist/web/public/i18n.33f930d7.js.br +0 -0
- package/dist/web/public/i18n.33f930d7.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.d4ec5957.js.br +0 -0
- package/dist/web/public/mobile.5ef2ce85.css.br +0 -0
- package/dist/web/public/panels-ui.b100727d.js.br +0 -0
- package/dist/web/public/panels-ui.b100727d.js.gz +0 -0
- package/dist/web/public/session-ui.5ac82f2c.js.br +0 -0
- package/dist/web/public/session-ui.5ac82f2c.js.gz +0 -0
- package/dist/web/public/settings-ui.d1d4fb99.js.br +0 -0
- package/dist/web/public/settings-ui.d1d4fb99.js.gz +0 -0
- package/dist/web/public/styles.77dc538f.css.br +0 -0
- package/dist/web/public/terminal-ui.d7c75ce8.js.br +0 -0
- package/dist/web/public/terminal-ui.d7c75ce8.js.gz +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.
|
|
2
|
+
"version": "1.49.0",
|
|
3
3
|
"highlights": [
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
4
|
+
"Open the tmux sessions you started yourself (#510)",
|
|
5
|
+
"Install without administrator rights (#768)",
|
|
6
|
+
"Docker Compose starts in two commands (#573)",
|
|
7
|
+
"Codex sessions can drive other sessions (#753)",
|
|
8
|
+
"Persian, Arabic and Hebrew read right to left (#715)",
|
|
9
|
+
"Edit history for remote sessions (#766)",
|
|
10
|
+
"The Files panel follows the agent into a git worktree (#772, #765)",
|
|
11
|
+
"One big folder no longer hides the rest (#614)"
|
|
12
12
|
]
|
|
13
13
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"case-routes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/case-routes.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"case-routes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/case-routes.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAoF1C,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAG3F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AA+D9C;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAE1E;AA0ID,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,GAAG,aAAa,GACxD,IAAI,CA4kEN"}
|
|
@@ -25,6 +25,7 @@ import { readAgentCaseMarker } from '../../agent-case-marker.js';
|
|
|
25
25
|
import { settingsWriteBlocker, writeHooksConfig } from '../../hooks-config.js';
|
|
26
26
|
import { canAccessOwned, getAuthUser, isAdmin, isWorkingDirAllowed, ownerFor, requireAdmin, resolveCasesDir, SETTINGS_PATH, validatePathWithinBase, parseBody, readJsonConfig, } from '../route-helpers.js';
|
|
27
27
|
import { assertSettingsWritable, mutateSettingsFile, SettingsWriteRefusedError, settingsWriteErrorResponse, } from '../settings-writer.js';
|
|
28
|
+
import { LinkedCasesWriteRefusedError, linkedCasesPath, mutateLinkedCases } from '../linked-cases-writer.js';
|
|
28
29
|
import { isMultiUserMode } from '../../config/multiuser.js';
|
|
29
30
|
import { SseEvent } from '../sse-events.js';
|
|
30
31
|
import { ownerLayoutKey } from '../../tab-layout-persistence.js';
|
|
@@ -33,7 +34,6 @@ import { dataPath, getDataDir } from '../../config/instance.js';
|
|
|
33
34
|
import { checkDockerAvailable, checkDockerImagePresent, checkDockerTmuxAvailable, ensureAgentBaseImage, DEFAULT_AGENT_IMAGE, dockerContainerName, dockerDisplayPath, probeAdoptableContainer, classifyAdoptContainerConflict, listDockerContainers, browseInContainer, dockerAdoptProbeModes, readDockerCases, readDockerHosts, removeDockerContainer, toSessionDocker, writeDockerCases, writeDockerHosts, } from '../../docker-hosts.js';
|
|
34
35
|
import { buildDockerRemoveCommand } from '../../tmux-manager.js';
|
|
35
36
|
import { checkRemoteTmuxAvailable, listRemoteCodemanSessions, readRemoteCases, readRemoteHosts, remoteDisplayPath, writeRemoteCases, writeRemoteHosts, } from '../../remote-hosts.js';
|
|
36
|
-
const LINKED_CASES_FILE = dataPath('linked-cases.json');
|
|
37
37
|
const CODEMAN_CONFIG_DIR = getDataDir();
|
|
38
38
|
const SAFE_CASE_NAME = /^[a-zA-Z0-9_-]+$/;
|
|
39
39
|
const DOCKER_EXPORTS_DIR = dataPath('docker-exports');
|
|
@@ -110,10 +110,19 @@ function agentCreatedInfo(marker) {
|
|
|
110
110
|
...(marker.mode ? { mode: marker.mode } : {}),
|
|
111
111
|
};
|
|
112
112
|
}
|
|
113
|
-
/**
|
|
113
|
+
/**
|
|
114
|
+
* Read and parse linked-cases.json, returning empty object on missing/invalid file. For reads only:
|
|
115
|
+
* every write goes through `mutateLinkedCases()`, which re-reads the file inside its own critical
|
|
116
|
+
* section and refuses one that does not parse.
|
|
117
|
+
*/
|
|
114
118
|
async function readLinkedCases() {
|
|
115
|
-
return readJsonConfig(
|
|
119
|
+
return readJsonConfig(linkedCasesPath(), 'linked cases', {});
|
|
120
|
+
}
|
|
121
|
+
/** A conflict thrown from inside a `mutateLinkedCases()` callback: answered 409, nothing written. */
|
|
122
|
+
function linkedConflict(message) {
|
|
123
|
+
return Object.assign(new Error(message), { conflict: true });
|
|
116
124
|
}
|
|
125
|
+
const isLinkedConflict = (err) => err.conflict === true;
|
|
117
126
|
/**
|
|
118
127
|
* Resolve a case name to its directory path, checking linked cases first, then the
|
|
119
128
|
* user's case space (per-user in multi-user mode, the shared CASES_DIR otherwise).
|
|
@@ -422,17 +431,12 @@ export function registerCaseRoutes(app, ctx) {
|
|
|
422
431
|
made.push(join(casePath, 'CLAUDE.md'));
|
|
423
432
|
made.push(join(casePath, '.claude')); // before the write, so a half-written one is undone too
|
|
424
433
|
await writeHooksConfig(casePath);
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
const fresh = await readLinkedCases();
|
|
432
|
-
if (fresh[name])
|
|
433
|
-
throw Object.assign(new Error(`Case "${name}" was just linked`), { conflict: true });
|
|
434
|
-
fresh[name] = casePath;
|
|
435
|
-
await fs.writeFile(LINKED_CASES_FILE, JSON.stringify(fresh, null, 2));
|
|
434
|
+
// The check above ran before the scaffold; a case linked since then wins and this call is undone.
|
|
435
|
+
await mutateLinkedCases((current) => {
|
|
436
|
+
if (current[name])
|
|
437
|
+
throw linkedConflict(`Case "${name}" was just linked`);
|
|
438
|
+
return { ...current, [name]: casePath };
|
|
439
|
+
});
|
|
436
440
|
ctx.broadcast(SseEvent.CaseCreated, { name, path: casePath });
|
|
437
441
|
return { success: true, data: { case: { name, path: casePath } } };
|
|
438
442
|
}
|
|
@@ -441,10 +445,14 @@ export function registerCaseRoutes(app, ctx) {
|
|
|
441
445
|
// entries inside the empty folder the user picked; never anything else.
|
|
442
446
|
for (const p of made.reverse())
|
|
443
447
|
await fs.rm(p, { recursive: true, force: true }).catch(() => undefined);
|
|
444
|
-
if (err
|
|
448
|
+
if (isLinkedConflict(err)) {
|
|
445
449
|
reply.code(409);
|
|
446
450
|
return createErrorResponse(ApiErrorCode.ALREADY_EXISTS, getErrorMessage(err));
|
|
447
451
|
}
|
|
452
|
+
if (err instanceof LinkedCasesWriteRefusedError) {
|
|
453
|
+
reply.code(409);
|
|
454
|
+
return createErrorResponse(ApiErrorCode.CONFLICT, err.message);
|
|
455
|
+
}
|
|
448
456
|
reply.code(500);
|
|
449
457
|
return createErrorResponse(ApiErrorCode.OPERATION_FAILED, getErrorMessage(err));
|
|
450
458
|
}
|
|
@@ -583,7 +591,7 @@ export function registerCaseRoutes(app, ctx) {
|
|
|
583
591
|
: prepared.code === 'EXISTS'
|
|
584
592
|
? ApiErrorCode.ALREADY_EXISTS
|
|
585
593
|
: ApiErrorCode.INVALID_INPUT, prepared.code === 'EXISTS'
|
|
586
|
-
? `${customPath} already has files in it.
|
|
594
|
+
? `${customPath} already has files in it. The clone target must be empty or not exist yet.`
|
|
587
595
|
: prepared.reason);
|
|
588
596
|
}
|
|
589
597
|
intoEmptyDir = prepared.existedEmpty;
|
|
@@ -708,20 +716,21 @@ export function registerCaseRoutes(app, ctx) {
|
|
|
708
716
|
warnings.push(`Case scaffolding was incomplete: ${getErrorMessage(err)}`);
|
|
709
717
|
}
|
|
710
718
|
if (customPath !== undefined) {
|
|
711
|
-
//
|
|
712
|
-
//
|
|
719
|
+
// The name was checked before git ran, which can take minutes. Checked again where the entry is
|
|
720
|
+
// written, so of two clones that claimed one name only the first to finish links it.
|
|
713
721
|
try {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
722
|
+
await mutateLinkedCases((current) => {
|
|
723
|
+
if (current[name])
|
|
724
|
+
throw linkedConflict(`Case "${name}" was linked while the clone ran`);
|
|
725
|
+
return { ...current, [name]: casePath };
|
|
726
|
+
});
|
|
719
727
|
}
|
|
720
728
|
catch (err) {
|
|
721
729
|
// The clone itself succeeded: keep the folder (the user waited for it) and say where it is.
|
|
722
|
-
const conflict = err
|
|
723
|
-
|
|
724
|
-
|
|
730
|
+
const conflict = isLinkedConflict(err);
|
|
731
|
+
const refused = err instanceof LinkedCasesWriteRefusedError;
|
|
732
|
+
reply.code(conflict || refused ? 409 : 500);
|
|
733
|
+
return createErrorResponse(conflict ? ApiErrorCode.ALREADY_EXISTS : refused ? ApiErrorCode.CONFLICT : ApiErrorCode.OPERATION_FAILED, `Cloned into ${casePath} but could not register the case (${getErrorMessage(err)}). Use Link Existing to add it.`);
|
|
725
734
|
}
|
|
726
735
|
}
|
|
727
736
|
ctx.broadcast(SseEvent.CaseCreated, { name, path: casePath });
|
|
@@ -1397,24 +1406,21 @@ export function registerCaseRoutes(app, ctx) {
|
|
|
1397
1406
|
if (existsSync(casePath)) {
|
|
1398
1407
|
return createErrorResponse(ApiErrorCode.ALREADY_EXISTS, 'A case with this name already exists in codeman-cases.');
|
|
1399
1408
|
}
|
|
1400
|
-
//
|
|
1401
|
-
const linkedCases = await readLinkedCases();
|
|
1402
|
-
// Check if name is already linked
|
|
1403
|
-
if (linkedCases[name]) {
|
|
1404
|
-
return createErrorResponse(ApiErrorCode.ALREADY_EXISTS, `Case "${name}" is already linked to ${linkedCases[name]}`);
|
|
1405
|
-
}
|
|
1406
|
-
// Save the linked case
|
|
1407
|
-
linkedCases[name] = expandedPath;
|
|
1409
|
+
// The name check and the write are one step, so two links of one name cannot both succeed.
|
|
1408
1410
|
try {
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1411
|
+
await mutateLinkedCases((current) => {
|
|
1412
|
+
if (current[name])
|
|
1413
|
+
throw linkedConflict(`Case "${name}" is already linked to ${current[name]}`);
|
|
1414
|
+
return { ...current, [name]: expandedPath };
|
|
1415
|
+
});
|
|
1414
1416
|
ctx.broadcast(SseEvent.CaseLinked, { name, path: expandedPath });
|
|
1415
1417
|
return { success: true, data: { case: { name, path: expandedPath } } };
|
|
1416
1418
|
}
|
|
1417
1419
|
catch (err) {
|
|
1420
|
+
if (isLinkedConflict(err))
|
|
1421
|
+
return createErrorResponse(ApiErrorCode.ALREADY_EXISTS, getErrorMessage(err));
|
|
1422
|
+
if (err instanceof LinkedCasesWriteRefusedError)
|
|
1423
|
+
return createErrorResponse(ApiErrorCode.CONFLICT, err.message);
|
|
1418
1424
|
return createErrorResponse(ApiErrorCode.OPERATION_FAILED, getErrorMessage(err));
|
|
1419
1425
|
}
|
|
1420
1426
|
});
|
|
@@ -1484,14 +1490,22 @@ export function registerCaseRoutes(app, ctx) {
|
|
|
1484
1490
|
// must not unlink one either; skip so control falls through to their local delete.
|
|
1485
1491
|
const linkedCases = await readLinkedCases();
|
|
1486
1492
|
if (linkedCases[name] && (!isMultiUserMode() || isAdmin(req))) {
|
|
1487
|
-
delete linkedCases[name];
|
|
1488
1493
|
try {
|
|
1489
|
-
|
|
1494
|
+
// Removes this one entry from the registry as it is now, keeping any linked since the read above.
|
|
1495
|
+
await mutateLinkedCases((current) => {
|
|
1496
|
+
if (!Object.hasOwn(current, name))
|
|
1497
|
+
return undefined;
|
|
1498
|
+
const next = { ...current };
|
|
1499
|
+
delete next[name];
|
|
1500
|
+
return next;
|
|
1501
|
+
});
|
|
1490
1502
|
ctx.broadcast(SseEvent.CaseDeleted, { name, type: 'unlinked' });
|
|
1491
1503
|
await forgetCaseDefault();
|
|
1492
1504
|
return { success: true, data: { name } };
|
|
1493
1505
|
}
|
|
1494
1506
|
catch (err) {
|
|
1507
|
+
if (err instanceof LinkedCasesWriteRefusedError)
|
|
1508
|
+
return createErrorResponse(ApiErrorCode.CONFLICT, err.message);
|
|
1495
1509
|
return createErrorResponse(ApiErrorCode.OPERATION_FAILED, getErrorMessage(err));
|
|
1496
1510
|
}
|
|
1497
1511
|
}
|
|
@@ -1660,7 +1674,6 @@ export function registerCaseRoutes(app, ctx) {
|
|
|
1660
1674
|
}
|
|
1661
1675
|
if (moveFolder)
|
|
1662
1676
|
await moveCaseFolder(oldPath, newPath);
|
|
1663
|
-
const originalLinks = isLinked ? await readLinkedCases() : undefined;
|
|
1664
1677
|
// The settings.json values this rename replaced, set only once it wrote them. A failure puts back
|
|
1665
1678
|
// these keys alone, so a settings save that landed in between is kept.
|
|
1666
1679
|
let settingsUndo;
|
|
@@ -1681,17 +1694,20 @@ export function registerCaseRoutes(app, ctx) {
|
|
|
1681
1694
|
settingsUndo = Object.fromEntries(keys.map((k) => [k, before[k]]));
|
|
1682
1695
|
}
|
|
1683
1696
|
}
|
|
1684
|
-
if (
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1697
|
+
if (isLinked) {
|
|
1698
|
+
// Checked again where the entry is written: the checks above ran before the sessions were
|
|
1699
|
+
// stopped and the folder moved. A failed write leaves the registry as it was.
|
|
1700
|
+
await mutateLinkedCases((current) => {
|
|
1701
|
+
if (current[newName])
|
|
1702
|
+
throw new CaseRenameError('EXISTS', `A case named "${newName}" already exists`);
|
|
1703
|
+
if (current[name] !== oldPath) {
|
|
1704
|
+
throw new CaseRenameError('BUSY', `Case "${name}" was changed while it was being renamed`);
|
|
1705
|
+
}
|
|
1706
|
+
const links = { ...current };
|
|
1707
|
+
delete links[name];
|
|
1708
|
+
links[newName] = newPath;
|
|
1709
|
+
return links;
|
|
1710
|
+
});
|
|
1695
1711
|
}
|
|
1696
1712
|
}
|
|
1697
1713
|
catch (err) {
|
|
@@ -1738,6 +1754,9 @@ export function registerCaseRoutes(app, ctx) {
|
|
|
1738
1754
|
: ApiErrorCode.OPERATION_FAILED;
|
|
1739
1755
|
return fail(status, code, err.message, { renameCode: err.code, stoppedSessions: stopped });
|
|
1740
1756
|
}
|
|
1757
|
+
if (err instanceof LinkedCasesWriteRefusedError) {
|
|
1758
|
+
return fail(409, ApiErrorCode.CONFLICT, err.message, { stoppedSessions: stopped });
|
|
1759
|
+
}
|
|
1741
1760
|
return fail(500, ApiErrorCode.OPERATION_FAILED, getErrorMessage(err), { stoppedSessions: stopped });
|
|
1742
1761
|
}
|
|
1743
1762
|
});
|