aicodeman 1.24.7 → 1.26.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/dist/agent-case-marker.d.ts +91 -0
- package/dist/agent-case-marker.d.ts.map +1 -0
- package/dist/agent-case-marker.js +151 -0
- package/dist/agent-case-marker.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +17 -2
- package/dist/cli.js.map +1 -1
- package/dist/config/base-path.d.ts +55 -0
- package/dist/config/base-path.d.ts.map +1 -0
- package/dist/config/base-path.js +105 -0
- package/dist/config/base-path.js.map +1 -0
- package/dist/config/cli-registry/schema.d.ts +2 -0
- package/dist/config/cli-registry/schema.d.ts.map +1 -1
- package/dist/config/cli-registry/schema.js +1 -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 +4 -1
- package/dist/config/cli-registry/stock.js.map +1 -1
- package/dist/config/cli-registry/types.d.ts +9 -0
- package/dist/config/cli-registry/types.d.ts.map +1 -1
- package/dist/daemon-control.d.ts +2 -0
- package/dist/daemon-control.d.ts.map +1 -1
- package/dist/daemon-control.js +2 -0
- package/dist/daemon-control.js.map +1 -1
- package/dist/docker-export.d.ts.map +1 -1
- package/dist/docker-export.js +8 -1
- package/dist/docker-export.js.map +1 -1
- package/dist/docker-hosts.d.ts +97 -3
- package/dist/docker-hosts.d.ts.map +1 -1
- package/dist/docker-hosts.js +260 -4
- package/dist/docker-hosts.js.map +1 -1
- package/dist/hooks-config.d.ts +25 -0
- package/dist/hooks-config.d.ts.map +1 -1
- package/dist/hooks-config.js +106 -6
- package/dist/hooks-config.js.map +1 -1
- package/dist/session.d.ts +31 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +101 -5
- package/dist/session.js.map +1 -1
- package/dist/tmux-manager.d.ts.map +1 -1
- package/dist/tmux-manager.js +54 -8
- package/dist/tmux-manager.js.map +1 -1
- package/dist/types/api.d.ts +51 -1
- package/dist/types/api.d.ts.map +1 -1
- package/dist/types/api.js.map +1 -1
- package/dist/types/session.d.ts +49 -0
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/session.js.map +1 -1
- package/dist/web/middleware/auth.d.ts +3 -3
- package/dist/web/middleware/auth.d.ts.map +1 -1
- package/dist/web/middleware/auth.js +13 -11
- package/dist/web/middleware/auth.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.3a89ac57.js +38 -0
- package/dist/web/public/app.3a89ac57.js.br +0 -0
- package/dist/web/public/app.3a89ac57.js.gz +0 -0
- package/dist/web/public/approvals-ui.js.gz +0 -0
- package/dist/web/public/{constants.7bd482dc.js → constants.7a635e34.js} +58 -0
- package/dist/web/public/constants.7a635e34.js.br +0 -0
- package/dist/web/public/constants.7a635e34.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/entrance-animations.js.gz +0 -0
- package/dist/web/public/home-sessions.js.gz +0 -0
- package/dist/web/public/i18n.9db8c4e0.js +1 -0
- package/dist/web/public/i18n.9db8c4e0.js.br +0 -0
- package/dist/web/public/i18n.9db8c4e0.js.gz +0 -0
- package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
- package/dist/web/public/index.html +46 -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.87f14251.js.gz +0 -0
- package/dist/web/public/{keyboard-accessory.0454a3e9.js → keyboard-accessory.daf18302.js} +8 -4
- package/dist/web/public/keyboard-accessory.daf18302.js.br +0 -0
- package/dist/web/public/keyboard-accessory.daf18302.js.gz +0 -0
- package/dist/web/public/manifest.json +2 -1
- package/dist/web/public/mobile-handlers.0f83a608.js.gz +0 -0
- package/dist/web/public/mobile-overview.js.gz +0 -0
- package/dist/web/public/{mobile.87674154.css → mobile.7112f27d.css} +1 -1
- package/dist/web/public/{mobile.87674154.css.br → mobile.7112f27d.css.br} +0 -0
- package/dist/web/public/mobile.7112f27d.css.gz +0 -0
- package/dist/web/public/{notification-manager.5d229063.js → notification-manager.36ea4624.js} +1 -1
- package/dist/web/public/notification-manager.36ea4624.js.br +0 -0
- package/dist/web/public/notification-manager.36ea4624.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/{panels-ui.e6e06034.js → panels-ui.db4785bf.js} +8 -8
- package/dist/web/public/panels-ui.db4785bf.js.br +0 -0
- package/dist/web/public/panels-ui.db4785bf.js.gz +0 -0
- package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
- package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
- package/dist/web/public/readmymind-ui.js.gz +0 -0
- package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/session-lineage.js.gz +0 -0
- package/dist/web/public/session-ui.a9c126c6.js +77 -0
- package/dist/web/public/session-ui.a9c126c6.js.br +0 -0
- package/dist/web/public/session-ui.a9c126c6.js.gz +0 -0
- package/dist/web/public/{settings-ui.1ed208dd.js → settings-ui.554f9cdd.js} +1 -1
- package/dist/web/public/settings-ui.554f9cdd.js.br +0 -0
- package/dist/web/public/settings-ui.554f9cdd.js.gz +0 -0
- package/dist/web/public/styles.298f81f2.css +1 -0
- package/dist/web/public/styles.298f81f2.css.br +0 -0
- package/dist/web/public/{styles.421b36d4.css.gz → styles.298f81f2.css.gz} +0 -0
- package/dist/web/public/subagent-windows.e6ca799f.js.gz +0 -0
- package/dist/web/public/sw.js +13 -6
- package/dist/web/public/sw.js.br +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/tab-rail-resize.42c24949.js.gz +0 -0
- package/dist/web/public/terminal-ui.70445f81.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/upload.html.gz +0 -0
- package/dist/web/public/vendor/dompurify.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.bd6882b8.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.6fee72f2.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.4ec670b0.js → voice-input.c4b51eb6.js} +1 -1
- package/dist/web/public/voice-input.c4b51eb6.js.br +0 -0
- package/dist/web/public/voice-input.c4b51eb6.js.gz +0 -0
- package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
- package/dist/web/public/webview-tabs.js +3 -1
- package/dist/web/public/webview-tabs.js.br +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/route-helpers.d.ts +18 -0
- package/dist/web/route-helpers.d.ts.map +1 -1
- package/dist/web/route-helpers.js +28 -0
- package/dist/web/route-helpers.js.map +1 -1
- package/dist/web/routes/case-routes.d.ts.map +1 -1
- package/dist/web/routes/case-routes.js +261 -6
- package/dist/web/routes/case-routes.js.map +1 -1
- package/dist/web/routes/file-routes.d.ts.map +1 -1
- package/dist/web/routes/file-routes.js +62 -4
- package/dist/web/routes/file-routes.js.map +1 -1
- package/dist/web/routes/hook-event-routes.d.ts.map +1 -1
- package/dist/web/routes/hook-event-routes.js +29 -3
- package/dist/web/routes/hook-event-routes.js.map +1 -1
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +139 -59
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/routes/webview-routes.d.ts +2 -2
- package/dist/web/routes/webview-routes.d.ts.map +1 -1
- package/dist/web/routes/webview-routes.js +23 -21
- package/dist/web/routes/webview-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +30 -0
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +78 -0
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/server.d.ts +4 -2
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +75 -12
- package/dist/web/server.js.map +1 -1
- package/dist/web/sse-events.d.ts +4 -2
- package/dist/web/sse-events.d.ts.map +1 -1
- package/dist/web/sse-events.js +4 -2
- package/dist/web/sse-events.js.map +1 -1
- package/dist/web/webview-proxy.d.ts +16 -7
- package/dist/web/webview-proxy.d.ts.map +1 -1
- package/dist/web/webview-proxy.js +28 -15
- package/dist/web/webview-proxy.js.map +1 -1
- package/package.json +8 -7
- package/skills/codeman/SKILL.md +14 -10
- package/skills/codeman/preamble.sh +6 -3
- package/skills/codeman/reference/endpoints.md +10 -0
- package/skills/codeman/reference/recipes.md +1 -1
- package/skills/codeman/reference/verbs.md +26 -1
- package/dist/web/public/app.a5d2fc08.js +0 -38
- package/dist/web/public/app.a5d2fc08.js.br +0 -0
- package/dist/web/public/app.a5d2fc08.js.gz +0 -0
- package/dist/web/public/constants.7bd482dc.js.br +0 -0
- package/dist/web/public/constants.7bd482dc.js.gz +0 -0
- package/dist/web/public/i18n.9ae15b51.js +0 -1
- package/dist/web/public/i18n.9ae15b51.js.br +0 -0
- package/dist/web/public/i18n.9ae15b51.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.0454a3e9.js.br +0 -0
- package/dist/web/public/keyboard-accessory.0454a3e9.js.gz +0 -0
- package/dist/web/public/mobile.87674154.css.gz +0 -0
- package/dist/web/public/notification-manager.5d229063.js.br +0 -0
- package/dist/web/public/notification-manager.5d229063.js.gz +0 -0
- package/dist/web/public/panels-ui.e6e06034.js.br +0 -0
- package/dist/web/public/panels-ui.e6e06034.js.gz +0 -0
- package/dist/web/public/session-ui.c558f981.js +0 -68
- package/dist/web/public/session-ui.c558f981.js.br +0 -0
- package/dist/web/public/session-ui.c558f981.js.gz +0 -0
- package/dist/web/public/settings-ui.1ed208dd.js.br +0 -0
- package/dist/web/public/settings-ui.1ed208dd.js.gz +0 -0
- package/dist/web/public/styles.421b36d4.css +0 -1
- package/dist/web/public/styles.421b36d4.css.br +0 -0
- package/dist/web/public/voice-input.4ec670b0.js.br +0 -0
- package/dist/web/public/voice-input.4ec670b0.js.gz +0 -0
|
@@ -19,7 +19,8 @@ import { ownerLayoutKey } from '../../tab-layout-persistence.js';
|
|
|
19
19
|
import { TabLayoutValidationError } from '../../tab-layout.js';
|
|
20
20
|
import { sessionWaits, resolveWaitSignals, sessionHookOptions, signalForStatus, WaitCapacityError, } from '../session-wait-registry.js';
|
|
21
21
|
import { clampWaitMs, MAX_BUFFER_SCAN_BYTES } from '../../config/agent-wait.js';
|
|
22
|
-
import { autoConfigureRalph, canAccessOwned, CASES_DIR, findPersistedSessionOrFail, findSessionOrFail, getAuthUser, isAdmin, isWorkingDirAllowed, ownerFor, parseBody, persistAndBroadcastSession, resolveCasesDir, resolveParentSessionId, sessionCapacityMessage, SETTINGS_PATH, validatePathWithinBase, } from '../route-helpers.js';
|
|
22
|
+
import { autoConfigureRalph, canAccessOwned, CASES_DIR, findPersistedSessionOrFail, findSessionOrFail, getAuthUser, isAdmin, isWorkingDirAllowed, ownerFor, parseBody, persistAndBroadcastSession, resolveAgentCaseOrigin, resolveCasesDir, resolveParentSessionId, sessionCapacityMessage, SETTINGS_PATH, validatePathWithinBase, } from '../route-helpers.js';
|
|
23
|
+
import { buildAgentCaseMarker, writeAgentCaseMarker } from '../../agent-case-marker.js';
|
|
23
24
|
import { canUsernameRunPrivilegedCommands, resolveClaudeModeForUsername } from '../../user-store.js';
|
|
24
25
|
import { enabledClis, getCli } from '../../config/cli-registry/registry.js';
|
|
25
26
|
import { resolveCliLaunchError } from '../../utils/cli-launcher.js';
|
|
@@ -38,7 +39,7 @@ import { MAX_INPUT_LENGTH, MAX_SESSION_NAME_LENGTH } from '../../config/terminal
|
|
|
38
39
|
import { MAX_PASTE_IMAGE_BYTES } from '../../config/buffer-limits.js';
|
|
39
40
|
import { dataPath, getDataDir } from '../../config/instance.js';
|
|
40
41
|
import { checkRemoteTmuxAvailable, readRemoteCases, readRemoteHosts, toAttachedSessionRemote, toSessionRemote, } from '../../remote-hosts.js';
|
|
41
|
-
import { checkDockerAvailable, checkDockerConfigDrift, checkDockerTmuxAvailable, ensureAgentBaseImage, DEFAULT_AGENT_IMAGE, persistDockerCaseClaudeSessionId, readDockerCases, readDockerHosts, toSessionDocker, } from '../../docker-hosts.js';
|
|
42
|
+
import { checkDockerAvailable, checkDockerConfigDrift, probeAdoptableContainer, checkDockerTmuxAvailable, ensureAgentBaseImage, DEFAULT_AGENT_IMAGE, persistDockerCaseClaudeSessionId, readDockerCases, readDockerHosts, toSessionDocker, } from '../../docker-hosts.js';
|
|
42
43
|
import { LRUMap } from '../../utils/lru-map.js';
|
|
43
44
|
import { findLatestOmpSessionId } from '../../utils/omp-session-resolver.js';
|
|
44
45
|
import { scanOmpSessionsHistory } from '../../omp-transcript.js';
|
|
@@ -1552,9 +1553,18 @@ export function registerSessionRoutes(app, ctx) {
|
|
|
1552
1553
|
// turn behind beats a viewer showing someone else's conversation.
|
|
1553
1554
|
const claudeHistoryPinCache = new LRUMap({ maxSize: 1024 });
|
|
1554
1555
|
async function resolveActiveClaudeSessionIdFromHistory(session, projectsDir) {
|
|
1556
|
+
// A pane whose conversation id came from its OWN hook needs no correlation:
|
|
1557
|
+
// $CODEMAN_SESSION_ID (the pane's env) -> data.session_id (the CLI's own
|
|
1558
|
+
// stdin JSON) is a first-hand binding that never looks at cwd, so it cannot
|
|
1559
|
+
// be stolen by a sibling pane, a closed tab, or a bare `claude` in a
|
|
1560
|
+
// terminal. Guessing can only be worse than the fact. This is also what
|
|
1561
|
+
// closes the hole below for a pane driven straight from tmux: it never
|
|
1562
|
+
// reaches `if (!submitAt)`.
|
|
1563
|
+
if (session.claudeSessionIdIsFirstHand)
|
|
1564
|
+
return null;
|
|
1555
1565
|
const submitAt = session.lastSubmitAt;
|
|
1556
1566
|
if (!submitAt)
|
|
1557
|
-
return null; //
|
|
1567
|
+
return null; // no anchor at all — nothing to credit
|
|
1558
1568
|
const cached = claudeHistoryPinCache.get(session.id);
|
|
1559
1569
|
if (cached && cached.submitAt === submitAt)
|
|
1560
1570
|
return cached.claudeSessionId;
|
|
@@ -1652,17 +1662,40 @@ export function registerSessionRoutes(app, ctx) {
|
|
|
1652
1662
|
return /^(?:<local-command|<command-name>|<task-notification>|<system-reminder>|<teammate-message\b|Another Claude session sent a message:|Base directory for this skill:)/i.test(text);
|
|
1653
1663
|
}
|
|
1654
1664
|
/**
|
|
1655
|
-
* Claude writes
|
|
1656
|
-
*
|
|
1657
|
-
*
|
|
1658
|
-
*
|
|
1665
|
+
* Claude writes an append-only event log: tool results arrive as user rows,
|
|
1666
|
+
* thinking/tool_use rows carry no text, and a prompt typed while the agent is
|
|
1667
|
+
* working is only ever recorded as an `attachment/queued_command` row. But one
|
|
1668
|
+
* assistant row IS one whole model message: measured across ~/.claude/projects
|
|
1669
|
+
* (CLI 2.1.220-2.1.251) no assistant row carries more than one content block
|
|
1670
|
+
* and no message id carries more than one text block, so there is nothing to
|
|
1671
|
+
* reassemble. Emit one card per row and group them with `turn` instead of
|
|
1672
|
+
* concatenating a human turn's replies into a single card (#169), which fused
|
|
1673
|
+
* up to 74 distinct model messages into one card. Splitting is safe for
|
|
1674
|
+
* markdown: no adjacent pair of assistant text rows in the corpus continues a
|
|
1675
|
+
* table, a list, or an open code fence.
|
|
1659
1676
|
*/
|
|
1660
1677
|
function parseClaudeResponseTranscript(content, full) {
|
|
1661
1678
|
let lastText = '';
|
|
1662
1679
|
let lastTimestamp = '';
|
|
1663
1680
|
const messages = [];
|
|
1664
|
-
|
|
1665
|
-
|
|
1681
|
+
// #169's replay guards, kept: they now SKIP a duplicated row instead of
|
|
1682
|
+
// concatenating it into the previous card.
|
|
1683
|
+
const currentUserFragments = new Set();
|
|
1684
|
+
const currentAssistantFragments = new Set();
|
|
1685
|
+
// Turn 0 is reserved for anything emitted before the first human prompt.
|
|
1686
|
+
let turn = 0;
|
|
1687
|
+
const pushUserMessage = (text, timestamp, queued) => {
|
|
1688
|
+
// A run of consecutive human inputs (a mid-turn queued burst) is ONE turn,
|
|
1689
|
+
// so the viewer renders it under one badge instead of one badge per line.
|
|
1690
|
+
if (messages.at(-1)?.role !== 'user')
|
|
1691
|
+
turn += 1;
|
|
1692
|
+
const message = { kind: 'prompt', label: 'Prompt', role: 'user', text, timestamp, turn };
|
|
1693
|
+
if (queued)
|
|
1694
|
+
message.queued = true;
|
|
1695
|
+
messages.push(message);
|
|
1696
|
+
currentUserFragments.add(text);
|
|
1697
|
+
currentAssistantFragments.clear();
|
|
1698
|
+
};
|
|
1666
1699
|
for (const line of content.split('\n')) {
|
|
1667
1700
|
if (!line)
|
|
1668
1701
|
continue;
|
|
@@ -1677,6 +1710,30 @@ export function registerSessionRoutes(app, ctx) {
|
|
|
1677
1710
|
// rows include repeated image dimensions and other UI-generated context.
|
|
1678
1711
|
if (entry.isSidechain)
|
|
1679
1712
|
continue;
|
|
1713
|
+
// A prompt typed while Claude is working is absorbed mid-turn and lives
|
|
1714
|
+
// ONLY in an attachment row, so it was lost outright. `origin.kind` and
|
|
1715
|
+
// `commandMode` separate the human's queue entries from the CLI's own:
|
|
1716
|
+
// measured over 57 real transcripts on 2026-09-01, 322 queued_command rows
|
|
1717
|
+
// split 163 `prompt`/`human` and 159 `task-notification`, and not one of
|
|
1718
|
+
// those 159 carries an `origin` key. The 163 human rows become 162 user
|
|
1719
|
+
// cards here — one is a verbatim repeat inside a still-unanswered user run
|
|
1720
|
+
// and is collapsed by the dedup guard below — out of 353 user cards total.
|
|
1721
|
+
if (entry.type === 'attachment') {
|
|
1722
|
+
if (!full)
|
|
1723
|
+
continue;
|
|
1724
|
+
const queued = entry.attachment;
|
|
1725
|
+
if (!queued || queued.type !== 'queued_command')
|
|
1726
|
+
continue;
|
|
1727
|
+
if (queued.origin?.kind !== 'human' || queued.commandMode !== 'prompt')
|
|
1728
|
+
continue;
|
|
1729
|
+
const text = typeof queued.prompt === 'string' ? queued.prompt.trim() : '';
|
|
1730
|
+
if (!text || isClaudeSyntheticUserMessage(entry, text))
|
|
1731
|
+
continue;
|
|
1732
|
+
if (currentUserFragments.has(text))
|
|
1733
|
+
continue;
|
|
1734
|
+
pushUserMessage(text, queued.timestamp || entry.timestamp, true);
|
|
1735
|
+
continue;
|
|
1736
|
+
}
|
|
1680
1737
|
if (entry.type === 'user') {
|
|
1681
1738
|
const text = extractClaudeText(entry.message?.content, '\n').trim();
|
|
1682
1739
|
// A tool_result block has no text block and naturally drops out here.
|
|
@@ -1684,21 +1741,13 @@ export function registerSessionRoutes(app, ctx) {
|
|
|
1684
1741
|
continue;
|
|
1685
1742
|
if (!full)
|
|
1686
1743
|
continue;
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
previous.text += `\n\n${text}`;
|
|
1695
|
-
currentUserFragments.add(text);
|
|
1696
|
-
}
|
|
1697
|
-
else {
|
|
1698
|
-
messages.push({ role: 'user', text, timestamp: entry.timestamp });
|
|
1699
|
-
currentUserFragments = new Set([text]);
|
|
1700
|
-
}
|
|
1701
|
-
currentAssistantFragments.clear();
|
|
1744
|
+
// Claude replays the initial user row while restoring a transcript, and
|
|
1745
|
+
// a CLI that also wrote an absorbed prompt as a user row would double it.
|
|
1746
|
+
// Both collapse here. The same prompt sent again AFTER a reply is a
|
|
1747
|
+
// legitimate second turn, because that reply cleared the set.
|
|
1748
|
+
if (currentUserFragments.has(text))
|
|
1749
|
+
continue;
|
|
1750
|
+
pushUserMessage(text, entry.timestamp, false);
|
|
1702
1751
|
continue;
|
|
1703
1752
|
}
|
|
1704
1753
|
if (entry.type !== 'assistant')
|
|
@@ -1710,20 +1759,11 @@ export function registerSessionRoutes(app, ctx) {
|
|
|
1710
1759
|
lastTimestamp = entry.timestamp || '';
|
|
1711
1760
|
if (!full)
|
|
1712
1761
|
continue;
|
|
1713
|
-
|
|
1714
|
-
if (
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
continue;
|
|
1719
|
-
previous.text += `\n\n${text}`;
|
|
1720
|
-
previous.timestamp = entry.timestamp || previous.timestamp;
|
|
1721
|
-
currentAssistantFragments.add(text);
|
|
1722
|
-
}
|
|
1723
|
-
else {
|
|
1724
|
-
messages.push({ role: 'assistant', text, timestamp: entry.timestamp });
|
|
1725
|
-
currentAssistantFragments = new Set([text]);
|
|
1726
|
-
}
|
|
1762
|
+
// Replayed snapshots repeat an identical text block inside one turn.
|
|
1763
|
+
if (currentAssistantFragments.has(text))
|
|
1764
|
+
continue;
|
|
1765
|
+
messages.push({ kind: 'response', label: 'Response', role: 'assistant', text, timestamp: entry.timestamp, turn });
|
|
1766
|
+
currentAssistantFragments.add(text);
|
|
1727
1767
|
currentUserFragments.clear();
|
|
1728
1768
|
}
|
|
1729
1769
|
return full ? { text: lastText, timestamp: lastTimestamp, messages } : { text: lastText, timestamp: lastTimestamp };
|
|
@@ -2527,7 +2567,10 @@ export function registerSessionRoutes(app, ctx) {
|
|
|
2527
2567
|
const capMsg = sessionCapacityMessage(ctx.sessions, owner);
|
|
2528
2568
|
if (capMsg)
|
|
2529
2569
|
return createErrorResponse(ApiErrorCode.SESSION_BUSY, capMsg);
|
|
2530
|
-
const { caseName = 'testcase', sessionName, mode = 'claude', modelOverride, openCodeConfig, codexConfig, geminiConfig, antigravityConfig, piConfig, grokConfig, deepSeekConfig, ompConfig, envOverrides, effort, parentSessionId, } = parseBody(QuickStartSchema, req.body);
|
|
2570
|
+
const { caseName = 'testcase', sessionName, mode = 'claude', modelOverride, openCodeConfig, codexConfig, geminiConfig, antigravityConfig, piConfig, grokConfig, deepSeekConfig, ompConfig, envOverrides, effort, parentSessionId, agentOrigin, } = parseBody(QuickStartSchema, req.body);
|
|
2571
|
+
// Resolved ONCE here: the same value labels a case directory this request creates
|
|
2572
|
+
// (agent-case-marker.ts) and draws the tab lineage line on the session below.
|
|
2573
|
+
const qsParentSessionId = resolveParentSessionId(ctx, req, parentSessionId, owner);
|
|
2531
2574
|
// Multi-user: shell mode is arbitrary host-account execution, gated by the grant.
|
|
2532
2575
|
// Resolve the owner's grant from the store so a GRANTED regular user is not wrongly denied.
|
|
2533
2576
|
if (getCli(mode)?.capabilities.privilegedCommandGate && !(await canUsernameRunPrivilegedCommands(owner))) {
|
|
@@ -2606,25 +2649,46 @@ export function registerSessionRoutes(app, ctx) {
|
|
|
2606
2649
|
return createErrorResponse(ApiErrorCode.OPERATION_FAILED, availability.error || 'docker daemon is not available');
|
|
2607
2650
|
}
|
|
2608
2651
|
const sessionDocker = toSessionDocker(host, dockerCase);
|
|
2609
|
-
//
|
|
2610
|
-
//
|
|
2611
|
-
//
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2652
|
+
// An ADOPTED container skips every image-side gate: we never run `docker
|
|
2653
|
+
// create`, so the image is the user's business, and `ensureAgentBaseImage`
|
|
2654
|
+
// would build/require an image that has nothing to do with their container.
|
|
2655
|
+
// The prerequisite that DOES still hold is tmux inside it, so probe the live
|
|
2656
|
+
// container (not the image) and refuse before launch rather than dead-paning.
|
|
2657
|
+
if (sessionDocker.owned === false) {
|
|
2658
|
+
const probe = await probeAdoptableContainer(sessionDocker, [mode]);
|
|
2659
|
+
if (!probe.ok) {
|
|
2660
|
+
return createErrorResponse(ApiErrorCode.OPERATION_FAILED, probe.error || 'container is not usable');
|
|
2661
|
+
}
|
|
2662
|
+
// The probe already exec'd into the container; carry its facts onto the
|
|
2663
|
+
// live session so the launch chain does not have to re-ask.
|
|
2664
|
+
sessionDocker.runsAsRoot = probe.runsAsRoot;
|
|
2665
|
+
// No `mode !== 'shell'` arm: a mode with no binary of its own is reported
|
|
2666
|
+
// available by the probe unconditionally, so this reads the same answer for it.
|
|
2667
|
+
if (!probe.availableModes?.includes(mode)) {
|
|
2668
|
+
return createErrorResponse(ApiErrorCode.OPERATION_FAILED, `"${mode}" is not installed in container "${sessionDocker.containerName}". Adoption never modifies the container — install it inside, or pick another mode.`);
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
else {
|
|
2672
|
+
// Ensure the base image exists, auto-building the default image on first use so
|
|
2673
|
+
// it is never a blocker. Dedup'd with any build kicked off at case-create, so
|
|
2674
|
+
// this awaits the SAME in-flight build rather than starting a second one.
|
|
2675
|
+
const ensured = await ensureAgentBaseImage(sessionDocker, sessionDocker.image, {
|
|
2676
|
+
onProgress: (line) => ctx.broadcast(SseEvent.DockerImageBuildProgress, { name: dockerCase.name, line }),
|
|
2677
|
+
});
|
|
2678
|
+
if (!ensured.ok) {
|
|
2679
|
+
return createErrorResponse(ApiErrorCode.OPERATION_FAILED, ensured.error || 'base image not available');
|
|
2680
|
+
}
|
|
2681
|
+
if (ensured.built) {
|
|
2682
|
+
ctx.broadcast(SseEvent.DockerImageBuildComplete, { name: dockerCase.name, image: sessionDocker.image });
|
|
2683
|
+
}
|
|
2684
|
+
// tmux is a hard prerequisite (the in-container tmux makes reconnect durable).
|
|
2685
|
+
// Skip the extra container-run probe for our OWN default image (the baked
|
|
2686
|
+
// Dockerfile always contains tmux); still verify a custom image.
|
|
2687
|
+
if (sessionDocker.image !== DEFAULT_AGENT_IMAGE) {
|
|
2688
|
+
const tmuxCheck = await checkDockerTmuxAvailable(sessionDocker);
|
|
2689
|
+
if (!tmuxCheck.ok) {
|
|
2690
|
+
return createErrorResponse(ApiErrorCode.OPERATION_FAILED, tmuxCheck.error || 'base image is missing tmux');
|
|
2691
|
+
}
|
|
2628
2692
|
}
|
|
2629
2693
|
}
|
|
2630
2694
|
// Config drift (docs/docker-cases-plan.md §4): the desired create-config no
|
|
@@ -2716,6 +2780,22 @@ export function registerSessionRoutes(app, ctx) {
|
|
|
2716
2780
|
mode !== 'omp') {
|
|
2717
2781
|
await writeHooksConfig(resolvedCasePath);
|
|
2718
2782
|
}
|
|
2783
|
+
// Label a directory an AGENT asked us to create, so the scratch workspaces a
|
|
2784
|
+
// long orchestration leaves behind can be told apart from the user's real
|
|
2785
|
+
// projects later (see agent-case-marker.ts). This is the only branch that may
|
|
2786
|
+
// write it: it is the only one that creates the directory, and a pre-existing
|
|
2787
|
+
// case must never be labelled. Best-effort — a failed marker must not fail the
|
|
2788
|
+
// spawn it decorates.
|
|
2789
|
+
const qsAgentOrigin = resolveAgentCaseOrigin(req, agentOrigin, qsParentSessionId);
|
|
2790
|
+
if (qsAgentOrigin) {
|
|
2791
|
+
await writeAgentCaseMarker(resolvedCasePath, buildAgentCaseMarker({
|
|
2792
|
+
createdBy: qsAgentOrigin,
|
|
2793
|
+
parentSessionId: qsParentSessionId,
|
|
2794
|
+
parentSessionName: qsParentSessionId ? ctx.sessions.get(qsParentSessionId)?.name : undefined,
|
|
2795
|
+
mode,
|
|
2796
|
+
owner,
|
|
2797
|
+
}));
|
|
2798
|
+
}
|
|
2719
2799
|
ctx.broadcast(SseEvent.CaseCreated, { name: caseName, path: resolvedCasePath });
|
|
2720
2800
|
}
|
|
2721
2801
|
catch (err) {
|
|
@@ -2833,7 +2913,7 @@ export function registerSessionRoutes(app, ctx) {
|
|
|
2833
2913
|
docker,
|
|
2834
2914
|
resumeSessionId: dockerResumeId,
|
|
2835
2915
|
tmuxHistoryLimit: qsTerminalHistoryConfig.tmuxHistoryLimit,
|
|
2836
|
-
parentSessionId:
|
|
2916
|
+
parentSessionId: qsParentSessionId,
|
|
2837
2917
|
});
|
|
2838
2918
|
// Auto-detect completion phrase from CLAUDE.md BEFORE broadcasting
|
|
2839
2919
|
// so the initial state already has the phrase configured (only if globally enabled)
|