termdock 1.4.71 → 1.4.72
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 +36 -0
- package/dist/client/assets/{ModelPreview-CG6vLwyw.js → ModelPreview-i9dS1A3I.js} +1 -1
- package/dist/client/assets/{abnfDiagram-VRR7QNED-CrXEZLWX.js → abnfDiagram-VRR7QNED-Cs-ty-HA.js} +1 -1
- package/dist/client/assets/{arc-DbKGR7Ex.js → arc-CIX5k_JD.js} +1 -1
- package/dist/client/assets/{architectureDiagram-ZJ3FMSHR-PF5BoUbI.js → architectureDiagram-ZJ3FMSHR-DnPnQoJy.js} +1 -1
- package/dist/client/assets/{blockDiagram-677ZJIJ3-D1gRzVWB.js → blockDiagram-677ZJIJ3-Dhew-L5v.js} +1 -1
- package/dist/client/assets/{c4Diagram-LMCZKHZV-CwvOLync.js → c4Diagram-LMCZKHZV-BSsEMeWn.js} +1 -1
- package/dist/client/assets/channel-CASnt1pP.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-BeRdP-xP.js → chunk-2Q5K7J3B-BWm--1Mo.js} +1 -1
- package/dist/client/assets/{chunk-32BRIVSS-CrqR5rvl.js → chunk-32BRIVSS-D_WFmuvb.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-CjbUr-ko.js → chunk-5VM5RSS4-CQdzqlQg.js} +1 -1
- package/dist/client/assets/{chunk-EX3LRPZG-BVEk3Wma.js → chunk-EX3LRPZG-Cm3zLQpt.js} +1 -1
- package/dist/client/assets/{chunk-JWPE2WC7-WitJe8kR.js → chunk-JWPE2WC7-Cmcv5sEN.js} +1 -1
- package/dist/client/assets/{chunk-MOJQB5TN-BCewlLYk.js → chunk-MOJQB5TN-Bam44EwJ.js} +1 -1
- package/dist/client/assets/{chunk-RYQCIY6F-CoP0AA4G.js → chunk-RYQCIY6F-DIVOuE8y.js} +1 -1
- package/dist/client/assets/{chunk-V7JOEXUC-CSZrnmp_.js → chunk-V7JOEXUC-BfOTf7yg.js} +1 -1
- package/dist/client/assets/{chunk-VR4S4FIN-BB-7zz-9.js → chunk-VR4S4FIN-DbiXn0dd.js} +1 -1
- package/dist/client/assets/{chunk-XXDRQBXY-CcZZlCik.js → chunk-XXDRQBXY-C4pOe4OC.js} +1 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-Da_SQWEq.js +1 -0
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-Da_SQWEq.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-DcmzUDwh.js → cose-bilkent-JH36ORCC-CCoSPmoc.js} +1 -1
- package/dist/client/assets/{cynefin-VYW2F7L2-C1XlDVoJ.js → cynefin-VYW2F7L2-B2lYz7sl.js} +1 -1
- package/dist/client/assets/{cynefinDiagram-TSTJHNR4-zY6Aet2S.js → cynefinDiagram-TSTJHNR4-Cd7crN9s.js} +1 -1
- package/dist/client/assets/{dagre-VKFMJZFB-DqGTi6zR.js → dagre-VKFMJZFB-q9Fj9HuQ.js} +1 -1
- package/dist/client/assets/{diagram-FQU43EPY-DStcsU7S.js → diagram-FQU43EPY-iqHy5YHW.js} +1 -1
- package/dist/client/assets/{diagram-G47NLZAW-Cs6IMNip.js → diagram-G47NLZAW-BE-jlh1B.js} +1 -1
- package/dist/client/assets/{diagram-NH7WQ7WH-yA2bpWN7.js → diagram-NH7WQ7WH-DmP7_WOC.js} +1 -1
- package/dist/client/assets/{diagram-OA4YK3LP-Dzf9M0PI.js → diagram-OA4YK3LP-CHx_k8Ar.js} +1 -1
- package/dist/client/assets/{diagram-WEI45ONY-D75lpQKA.js → diagram-WEI45ONY-D0wImpAF.js} +1 -1
- package/dist/client/assets/{ebnfDiagram-CCIWWBDH-CkQvIpWy.js → ebnfDiagram-CCIWWBDH-DyCB0rF8.js} +1 -1
- package/dist/client/assets/{erDiagram-Q63AITRT-Ca00Vjxz.js → erDiagram-Q63AITRT-SRcd1B2Z.js} +1 -1
- package/dist/client/assets/{flowDiagram-23GEKE2U-CGrnSTEp.js → flowDiagram-23GEKE2U-D0i9mFwt.js} +1 -1
- package/dist/client/assets/{ganttDiagram-NO4QXBWP-0SHfOIP4.js → ganttDiagram-NO4QXBWP-BfwGIYDn.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-IHSO6WYX-BeigYWvs.js → gitGraphDiagram-IHSO6WYX-BXkJQP7O.js} +1 -1
- package/dist/client/assets/index-BA-DqJM2.js +451 -0
- package/dist/client/assets/{infoDiagram-FWYZ7A6U-CiY1-xjN.js → infoDiagram-FWYZ7A6U-CM-Ebv9R.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-FXEZZL3T-CmtDjUm_.js → ishikawaDiagram-FXEZZL3T-U-qnP7xi.js} +1 -1
- package/dist/client/assets/{journeyDiagram-5HDEW3XC-B7qOr9LY.js → journeyDiagram-5HDEW3XC-D4RxvsuL.js} +1 -1
- package/dist/client/assets/{kanban-definition-HUTT4EX6-DO2P2Um3.js → kanban-definition-HUTT4EX6-Dm_asCYK.js} +1 -1
- package/dist/client/assets/{linear-B832hJt0.js → linear-Zhe8d635.js} +1 -1
- package/dist/client/assets/{mermaid.core-CsAzdiCU.js → mermaid.core-D_NWIuMY.js} +4 -4
- package/dist/client/assets/{mindmap-definition-LN4V7U3C-HI__vXhQ.js → mindmap-definition-LN4V7U3C-DXpNidd5.js} +1 -1
- package/dist/client/assets/{pegDiagram-2B236MQR--rZIwjXq.js → pegDiagram-2B236MQR-DdOwOEgu.js} +1 -1
- package/dist/client/assets/{pieDiagram-ENE6RG2P-D9jPf5x9.js → pieDiagram-ENE6RG2P-D_A9HoY_.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-ABIIQ3AL-DTCP5ji2.js → quadrantDiagram-ABIIQ3AL-D-Y3cLEF.js} +1 -1
- package/dist/client/assets/{railroadDiagram-RFXS5EU6-D2Gspvrl.js → railroadDiagram-RFXS5EU6-DD6eFV82.js} +1 -1
- package/dist/client/assets/{requirementDiagram-TGXJPOKE-DEJC02IW.js → requirementDiagram-TGXJPOKE-B59HJgL2.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-HTMAVEWB-opOhKPap.js → sankeyDiagram-HTMAVEWB-C2BvHMH8.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-DBY2YBRQ-_sz2CRAG.js → sequenceDiagram-DBY2YBRQ-3004tCRm.js} +1 -1
- package/dist/client/assets/{sizeCapture-X5ZJPWSS-D0Q2QkYF.js → sizeCapture-X5ZJPWSS-DCBhg8QW.js} +1 -1
- package/dist/client/assets/{stateDiagram-2N3HPSRC-VgeSQTtW.js → stateDiagram-2N3HPSRC-BVqiKVe0.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-Z0GHWyoF.js +1 -0
- package/dist/client/assets/{swimlanes-5IMT3BWC-ClvBvSl0.js → swimlanes-5IMT3BWC-DS6-sdEW.js} +2 -2
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-el4HDX0G.js +8 -0
- package/dist/client/assets/{timeline-definition-FHXFAJF6-BXNsrECV.js → timeline-definition-FHXFAJF6-wVOEmQwG.js} +1 -1
- package/dist/client/assets/{vennDiagram-L72KCM5P-BgwiqZd-.js → vennDiagram-L72KCM5P-DMZiSJDk.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-EHGQE667-DiqN5ERx.js → wardleyDiagram-EHGQE667-Dx5Aob4Y.js} +1 -1
- package/dist/client/assets/{xychartDiagram-FW5EYKEG-DQjFVqC6.js → xychartDiagram-FW5EYKEG-BqMhkhmW.js} +1 -1
- package/dist/client/index.html +1 -1
- package/dist/client/sw.js +1 -1
- package/dist/server/agent/autoTitle.js +3 -0
- package/dist/server/agent/pluginPackages.js +289 -0
- package/dist/server/agent/plugins.js +123 -19
- package/dist/server/agent/titleNamerCatalog.js +39 -8
- package/dist/server/cli.js +205 -14
- package/dist/server/entry.js +11 -2
- package/dist/server/routes/terminal.js +232 -29
- package/dist/server/utils/csrfProtection.js +4 -1
- package/dist/server/utils/settings.js +14 -0
- package/package.json +1 -1
- package/runtime-manifest.json +2 -2
- package/dist/client/assets/channel-JTEuKVy6.js +0 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-BiFJB_5s.js +0 -1
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-BiFJB_5s.js +0 -1
- package/dist/client/assets/index-D5PKdbhW.js +0 -451
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-BVh2FRO-.js +0 -1
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-C17V-kgm.js +0 -8
|
@@ -14,7 +14,7 @@ import { getPtyHostManager } from '../ptyhost/manager.js';
|
|
|
14
14
|
import { pathValidator } from '../utils/pathValidator.js';
|
|
15
15
|
import { TERMINAL, TMUX } from '../config.js';
|
|
16
16
|
import { localAccessManager } from '../utils/localAccess.js';
|
|
17
|
-
import { normalizeLocalAccessName, getLocaleSetting, setLocaleSetting, getContextDraftHeightSetting, setContextDraftHeightSetting, getAutoRenameAgentsSetting, setAutoRenameAgentsSetting, getAutoRenameNamerSetting, setAutoRenameNamerSetting, getAutoRenameModelsSetting, setAutoRenameModelsSetting, } from '../utils/settings.js';
|
|
17
|
+
import { normalizeLocalAccessName, getLocaleSetting, setLocaleSetting, getContextDraftHeightSetting, setContextDraftHeightSetting, getAutoRenameAgentsSetting, setAutoRenameAgentsSetting, getAutoRenameNamerSetting, setAutoRenameNamerSetting, getAutoRenameModelsSetting, setAutoRenameModelsSetting, getAutoRenameIntervalMinutesSetting, setAutoRenameIntervalMinutesSetting, } from '../utils/settings.js';
|
|
18
18
|
import { loadContextDraft, saveContextDraft } from '../utils/contextDraft.js';
|
|
19
19
|
import { getOnboardingServerUrl } from '../onboardingServer.js';
|
|
20
20
|
import { getFocusSequence, removeClientFocusState, scanFocusTrackingMode, setClientFocusState, } from '../utils/tmuxFocus.js';
|
|
@@ -23,12 +23,13 @@ import { agentBySlug, buildResumeCommand, detectAgentFromCommand, listAgents, }
|
|
|
23
23
|
import { applyAgentEvent, agentEventMatchesCurrentAgent, defaultAgentSessionState, parseAgentEvent, } from '../agent/session.js';
|
|
24
24
|
import { listAllHookAgents, refreshStaleHooksAtLaunch, installHooksForSlug, uninstallHooksForSlug, } from '../agent/installers.js';
|
|
25
25
|
import { loadPlugins, savePlugin, removePlugin, readPluginIcon, validateManifest, } from '../agent/plugins.js';
|
|
26
|
+
import { checkPluginPackageUpdate, commitPreparedPlugin, preparePluginPackage, } from '../agent/pluginPackages.js';
|
|
26
27
|
import { clearPluginAgents, registerPluginAgents } from '../agent/registry.js';
|
|
27
28
|
import { notifyAgentTransition, notifyTerminalExit } from '../notifications/pushService.js';
|
|
28
29
|
import { setClientViewingSession } from '../notifications/pushViewers.js';
|
|
29
30
|
import { getQuotaStatus, refreshQuota, startQuotaManager, } from '../quota/QuotaManager.js';
|
|
30
31
|
import { TermdockAutoUpdateManager } from '../utils/autoUpdate.js';
|
|
31
|
-
import { AUTO_TITLE_MIN_CONTEXT_CHARS, cleanTerminalContext, generateAgentTitle, isNewAgentSessionId, shouldReplaceAutoTitle, } from '../agent/autoTitle.js';
|
|
32
|
+
import { AUTO_TITLE_MIN_CONTEXT_CHARS, cleanTerminalContext, generateAgentTitle, isNewAgentSessionId, isAutoTitleReevaluationDue, shouldReplaceAutoTitle, } from '../agent/autoTitle.js';
|
|
32
33
|
import { getTitleNamerCatalog, invalidateTitleNamerCatalog } from '../agent/titleNamerCatalog.js';
|
|
33
34
|
const router = express.Router();
|
|
34
35
|
const execFileAsync = promisify(execFile);
|
|
@@ -2128,7 +2129,6 @@ function detectSessionAgent(session) {
|
|
|
2128
2129
|
return null;
|
|
2129
2130
|
}
|
|
2130
2131
|
const AUTO_TITLE_CONTEXT_LIMIT = 24_000;
|
|
2131
|
-
const AUTO_TITLE_REEVALUATE_MS = 60 * 60_000;
|
|
2132
2132
|
const autoTitlePending = new Set();
|
|
2133
2133
|
function appendAutoTitleContext(session, data) {
|
|
2134
2134
|
session.autoTitleContext = (session.autoTitleContext + data).slice(-AUTO_TITLE_CONTEXT_LIMIT);
|
|
@@ -2155,7 +2155,7 @@ async function maybeAutoRenameSession(sessionId, agent, rawContext) {
|
|
|
2155
2155
|
const record = globalSessionState.sessions.find((item) => item.backendSessionId === sessionId);
|
|
2156
2156
|
if (!record || (record.customName === true && !record.autoTitle))
|
|
2157
2157
|
return;
|
|
2158
|
-
if (record.autoTitle &&
|
|
2158
|
+
if (record.autoTitle && !isAutoTitleReevaluationDue(record.autoTitle.updatedAt, getAutoRenameIntervalMinutesSetting()))
|
|
2159
2159
|
return;
|
|
2160
2160
|
const context = cleanTerminalContext(rawContext);
|
|
2161
2161
|
// A new session gets one cheap attempt after its first real exchange. Short
|
|
@@ -2507,18 +2507,9 @@ function maybeRefreshGitStatusForAgent(sessionId, session) {
|
|
|
2507
2507
|
session.gitAgentActivitySeen = activity;
|
|
2508
2508
|
refreshGitStatus(sessionId, session);
|
|
2509
2509
|
}
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
*/
|
|
2514
|
-
function resolveAgentResumeCommand(sessionId, session) {
|
|
2515
|
-
const liveAgent = session.agent;
|
|
2516
|
-
const live = session.agentSession;
|
|
2517
|
-
if (liveAgent && live?.sessionId) {
|
|
2518
|
-
const cmd = buildResumeCommand(liveAgent, live.sessionId, live.launchArgv);
|
|
2519
|
-
if (cmd)
|
|
2520
|
-
return cmd;
|
|
2521
|
-
}
|
|
2510
|
+
function resolveAgentResumeTarget(sessionId, session) {
|
|
2511
|
+
// Resume always starts a new Agent process. Never offer it while the pane is
|
|
2512
|
+
// still inside an Agent TUI, even when that Agent reports a completed turn.
|
|
2522
2513
|
if (!isPaneAtShellPrompt(session))
|
|
2523
2514
|
return null;
|
|
2524
2515
|
const record = globalSessionState.sessions.find((s) => s.backendSessionId === sessionId);
|
|
@@ -2528,7 +2519,66 @@ function resolveAgentResumeCommand(sessionId, session) {
|
|
|
2528
2519
|
const agent = agentBySlug(persisted.slug);
|
|
2529
2520
|
if (!agent)
|
|
2530
2521
|
return null;
|
|
2531
|
-
|
|
2522
|
+
const command = buildResumeCommand(agent, persisted.sessionId, persisted.launchArgv);
|
|
2523
|
+
return command ? { slug: agent.slug, nativeSessionId: persisted.sessionId, command } : null;
|
|
2524
|
+
}
|
|
2525
|
+
function findActiveAgentResumeOwner(excludedBackendSessionId, target) {
|
|
2526
|
+
for (const [backendSessionId, candidate] of terminalSessions) {
|
|
2527
|
+
if (backendSessionId === excludedBackendSessionId || candidate.agent?.slug !== target.slug)
|
|
2528
|
+
continue;
|
|
2529
|
+
const record = globalSessionState.sessions.find((entry) => entry.backendSessionId === backendSessionId);
|
|
2530
|
+
const nativeSessionId = candidate.agentSession?.sessionId ?? record?.agentResume?.sessionId ?? null;
|
|
2531
|
+
if (nativeSessionId === target.nativeSessionId)
|
|
2532
|
+
return backendSessionId;
|
|
2533
|
+
}
|
|
2534
|
+
return null;
|
|
2535
|
+
}
|
|
2536
|
+
const codexSessionFileCache = new Map();
|
|
2537
|
+
async function findCodexSessionFile(nativeSessionId) {
|
|
2538
|
+
const cachedPath = codexSessionFileCache.get(nativeSessionId);
|
|
2539
|
+
if (cachedPath && fs.existsSync(cachedPath))
|
|
2540
|
+
return cachedPath;
|
|
2541
|
+
if (!/^[a-zA-Z0-9._-]{8,160}$/.test(nativeSessionId))
|
|
2542
|
+
return null;
|
|
2543
|
+
const root = path.join(os.homedir(), '.codex', 'sessions');
|
|
2544
|
+
const pending = [root];
|
|
2545
|
+
let visited = 0;
|
|
2546
|
+
while (pending.length > 0 && visited < 20_000) {
|
|
2547
|
+
const directory = pending.pop();
|
|
2548
|
+
let entries;
|
|
2549
|
+
try {
|
|
2550
|
+
entries = await fs.promises.readdir(directory, { withFileTypes: true });
|
|
2551
|
+
}
|
|
2552
|
+
catch {
|
|
2553
|
+
continue;
|
|
2554
|
+
}
|
|
2555
|
+
for (const entry of entries) {
|
|
2556
|
+
visited += 1;
|
|
2557
|
+
const candidate = path.join(directory, entry.name);
|
|
2558
|
+
if (entry.isDirectory())
|
|
2559
|
+
pending.push(candidate);
|
|
2560
|
+
else if (entry.isFile() && entry.name.endsWith(`-${nativeSessionId}.jsonl`)) {
|
|
2561
|
+
codexSessionFileCache.set(nativeSessionId, candidate);
|
|
2562
|
+
return candidate;
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
return null;
|
|
2567
|
+
}
|
|
2568
|
+
async function findExternalCodexWriter(target) {
|
|
2569
|
+
if (target.slug !== 'codex' || process.platform !== 'linux')
|
|
2570
|
+
return null;
|
|
2571
|
+
const sessionFile = await findCodexSessionFile(target.nativeSessionId);
|
|
2572
|
+
if (!sessionFile)
|
|
2573
|
+
return null;
|
|
2574
|
+
try {
|
|
2575
|
+
const { stdout } = await execFileAsync('lsof', ['-t', '--', sessionFile], { timeout: 2500, maxBuffer: 64 * 1024 });
|
|
2576
|
+
const pid = stdout.split(/\s+/).map(Number).find((value) => Number.isInteger(value) && value > 1);
|
|
2577
|
+
return pid ?? null;
|
|
2578
|
+
}
|
|
2579
|
+
catch {
|
|
2580
|
+
return null;
|
|
2581
|
+
}
|
|
2532
2582
|
}
|
|
2533
2583
|
/**
|
|
2534
2584
|
* Deliver a prompt into an agent's PTY: bracketed paste (so multi-line
|
|
@@ -4378,6 +4428,7 @@ async function getSettingsPayload() {
|
|
|
4378
4428
|
autoRenameAgents: getAutoRenameAgentsSetting(),
|
|
4379
4429
|
autoRenameNamer: getAutoRenameNamerSetting(),
|
|
4380
4430
|
autoRenameModels: getAutoRenameModelsSetting(),
|
|
4431
|
+
autoRenameIntervalMinutes: getAutoRenameIntervalMinutesSetting(),
|
|
4381
4432
|
localAccess: {
|
|
4382
4433
|
...localAccess,
|
|
4383
4434
|
interfaces,
|
|
@@ -4433,6 +4484,7 @@ router.put('/settings', async (req, res) => {
|
|
|
4433
4484
|
const requested = body.autoRenameAgents
|
|
4434
4485
|
.filter((slug) => typeof slug === 'string' && knownSlugs.has(slug));
|
|
4435
4486
|
setAutoRenameAgentsSetting(requested);
|
|
4487
|
+
invalidateTitleNamerCatalog();
|
|
4436
4488
|
}
|
|
4437
4489
|
if (typeof body.autoRenameNamer === 'string') {
|
|
4438
4490
|
const available = new Set((await getTitleNamerCatalog()).filter((entry) => entry.available).map((entry) => entry.slug));
|
|
@@ -4441,6 +4493,7 @@ router.put('/settings', async (req, res) => {
|
|
|
4441
4493
|
return;
|
|
4442
4494
|
}
|
|
4443
4495
|
setAutoRenameNamerSetting(body.autoRenameNamer);
|
|
4496
|
+
invalidateTitleNamerCatalog();
|
|
4444
4497
|
}
|
|
4445
4498
|
if (body.autoRenameModels && typeof body.autoRenameModels === 'object') {
|
|
4446
4499
|
const requested = body.autoRenameModels;
|
|
@@ -4458,6 +4511,12 @@ router.put('/settings', async (req, res) => {
|
|
|
4458
4511
|
}
|
|
4459
4512
|
setAutoRenameModelsSetting(models);
|
|
4460
4513
|
}
|
|
4514
|
+
if (typeof body.autoRenameIntervalMinutes === 'number'
|
|
4515
|
+
&& Number.isInteger(body.autoRenameIntervalMinutes)
|
|
4516
|
+
&& body.autoRenameIntervalMinutes >= 5
|
|
4517
|
+
&& body.autoRenameIntervalMinutes <= 1440) {
|
|
4518
|
+
setAutoRenameIntervalMinutesSetting(body.autoRenameIntervalMinutes);
|
|
4519
|
+
}
|
|
4461
4520
|
if (body.localAccess && typeof body.localAccess === 'object') {
|
|
4462
4521
|
const localAccessBody = body.localAccess;
|
|
4463
4522
|
if (localAccessBody.reset === true) {
|
|
@@ -4609,10 +4668,132 @@ router.get('/agent-plugins', (_req, res) => {
|
|
|
4609
4668
|
hasResume: p.manifest.resume !== undefined,
|
|
4610
4669
|
hasTitleNamer: p.manifest.titleNamer !== undefined,
|
|
4611
4670
|
hasIcon: p.iconPath !== null,
|
|
4671
|
+
sourceType: p.source?.type ?? 'manifest',
|
|
4672
|
+
source: p.source?.source ?? null,
|
|
4673
|
+
revision: p.source?.revision ?? null,
|
|
4674
|
+
latestRevision: p.source?.latestRevision ?? null,
|
|
4675
|
+
checkedAt: p.source?.checkedAt ?? null,
|
|
4676
|
+
updatedAt: p.source?.updatedAt ?? null,
|
|
4677
|
+
updateSupported: Boolean(p.source?.source && p.source.type !== 'manifest'),
|
|
4678
|
+
updateAvailable: Boolean(p.source?.revision && p.source.latestRevision && p.source.revision !== p.source.latestRevision),
|
|
4612
4679
|
})),
|
|
4613
4680
|
errors: errors.map((e) => ({ slug: e.slug, errors: e.errors, code: e.code, migration: e.migration })),
|
|
4614
4681
|
});
|
|
4615
4682
|
});
|
|
4683
|
+
function reloadAgentPlugins() {
|
|
4684
|
+
invalidateTitleNamerCatalog();
|
|
4685
|
+
clearPluginAgents();
|
|
4686
|
+
registerPluginAgents(loadPlugins().plugins);
|
|
4687
|
+
}
|
|
4688
|
+
router.post('/agent-plugins/install', async (req, res) => {
|
|
4689
|
+
const source = typeof req.body?.source === 'string' ? req.body.source.trim() : '';
|
|
4690
|
+
if (!source) {
|
|
4691
|
+
res.status(400).json({ error: 'Plugin source is required', code: 'AGENT_PLUGIN_SOURCE_REQUIRED' });
|
|
4692
|
+
return;
|
|
4693
|
+
}
|
|
4694
|
+
try {
|
|
4695
|
+
const prepared = await preparePluginPackage(source);
|
|
4696
|
+
const dir = await commitPreparedPlugin(prepared, false);
|
|
4697
|
+
reloadAgentPlugins();
|
|
4698
|
+
res.json({ slug: prepared.manifest.slug, dir, state: 'installed', source: prepared.metadata });
|
|
4699
|
+
}
|
|
4700
|
+
catch (error) {
|
|
4701
|
+
const detail = error;
|
|
4702
|
+
res.status(400).json({
|
|
4703
|
+
error: detail.message,
|
|
4704
|
+
code: detail.code ?? 'AGENT_PLUGIN_INSTALL_FAILED',
|
|
4705
|
+
migration: detail.migration,
|
|
4706
|
+
});
|
|
4707
|
+
}
|
|
4708
|
+
});
|
|
4709
|
+
router.post('/agent-plugins/:slug/check-update', async (req, res) => {
|
|
4710
|
+
const plugin = loadPlugins().plugins.find((entry) => entry.manifest.slug === req.params.slug);
|
|
4711
|
+
if (!plugin) {
|
|
4712
|
+
res.status(404).json({ error: `Plugin "${req.params.slug}" not found` });
|
|
4713
|
+
return;
|
|
4714
|
+
}
|
|
4715
|
+
try {
|
|
4716
|
+
res.json(await checkPluginPackageUpdate(plugin));
|
|
4717
|
+
}
|
|
4718
|
+
catch (error) {
|
|
4719
|
+
res.status(400).json({ error: error.message, code: 'AGENT_PLUGIN_UPDATE_CHECK_FAILED' });
|
|
4720
|
+
}
|
|
4721
|
+
});
|
|
4722
|
+
router.post('/agent-plugins/:slug/update', async (req, res) => {
|
|
4723
|
+
const slug = req.params.slug;
|
|
4724
|
+
const plugin = loadPlugins().plugins.find((entry) => entry.manifest.slug === slug);
|
|
4725
|
+
if (!plugin) {
|
|
4726
|
+
res.status(404).json({ error: `Plugin "${slug}" not found` });
|
|
4727
|
+
return;
|
|
4728
|
+
}
|
|
4729
|
+
if (!plugin.source?.source || plugin.source.type === 'manifest') {
|
|
4730
|
+
res.status(400).json({ error: 'This plugin has no update source', code: 'AGENT_PLUGIN_UPDATE_UNSUPPORTED' });
|
|
4731
|
+
return;
|
|
4732
|
+
}
|
|
4733
|
+
let prepared = null;
|
|
4734
|
+
let oldHooksRemoved = false;
|
|
4735
|
+
let committed = false;
|
|
4736
|
+
try {
|
|
4737
|
+
// Download and validate the complete replacement before touching the live
|
|
4738
|
+
// package or its currently installed hooks.
|
|
4739
|
+
prepared = await preparePluginPackage(plugin.source.source, slug);
|
|
4740
|
+
const hooksChanged = JSON.stringify(plugin.manifest.hooks ?? null) !== JSON.stringify(prepared.manifest.hooks ?? null);
|
|
4741
|
+
const titleCapabilityChanged = JSON.stringify(plugin.manifest.titleNamer ?? null) !== JSON.stringify(prepared.manifest.titleNamer ?? null);
|
|
4742
|
+
const hookState = (await listAllHookAgents()).find((entry) => entry.slug === slug)?.state;
|
|
4743
|
+
const restoreHooks = hookState === 'installed' || hookState === 'outdated';
|
|
4744
|
+
if (restoreHooks && plugin.manifest.hooks) {
|
|
4745
|
+
await uninstallHooksForSlug(slug);
|
|
4746
|
+
oldHooksRemoved = true;
|
|
4747
|
+
}
|
|
4748
|
+
const dir = await commitPreparedPlugin(prepared, true);
|
|
4749
|
+
prepared = null;
|
|
4750
|
+
committed = true;
|
|
4751
|
+
reloadAgentPlugins();
|
|
4752
|
+
let hookWarning = null;
|
|
4753
|
+
if (restoreHooks && hooksChanged) {
|
|
4754
|
+
hookWarning = 'Hook declarations changed during update. Review them, then install hooks again.';
|
|
4755
|
+
}
|
|
4756
|
+
else if (restoreHooks) {
|
|
4757
|
+
try {
|
|
4758
|
+
await installHooksForSlug(slug);
|
|
4759
|
+
}
|
|
4760
|
+
catch (error) {
|
|
4761
|
+
hookWarning = error.message;
|
|
4762
|
+
}
|
|
4763
|
+
}
|
|
4764
|
+
let titleWarning = null;
|
|
4765
|
+
if (titleCapabilityChanged) {
|
|
4766
|
+
setAutoRenameAgentsSetting(getAutoRenameAgentsSetting().filter((entry) => entry !== slug));
|
|
4767
|
+
if (getAutoRenameNamerSetting() === slug)
|
|
4768
|
+
setAutoRenameNamerSetting('auto');
|
|
4769
|
+
const models = getAutoRenameModelsSetting();
|
|
4770
|
+
delete models[slug];
|
|
4771
|
+
setAutoRenameModelsSetting(models);
|
|
4772
|
+
invalidateTitleNamerCatalog();
|
|
4773
|
+
titleWarning = 'Title command declarations changed during update. Automatic titles for this plugin were disabled; review and enable them again.';
|
|
4774
|
+
}
|
|
4775
|
+
res.json({
|
|
4776
|
+
slug,
|
|
4777
|
+
dir,
|
|
4778
|
+
state: 'updated',
|
|
4779
|
+
source: loadPlugins().plugins.find((entry) => entry.manifest.slug === slug)?.source,
|
|
4780
|
+
hookWarning,
|
|
4781
|
+
titleWarning,
|
|
4782
|
+
});
|
|
4783
|
+
}
|
|
4784
|
+
catch (error) {
|
|
4785
|
+
if (prepared)
|
|
4786
|
+
await prepared.cleanup().catch(() => undefined);
|
|
4787
|
+
if (oldHooksRemoved && !committed)
|
|
4788
|
+
await installHooksForSlug(slug).catch(() => undefined);
|
|
4789
|
+
const detail = error;
|
|
4790
|
+
res.status(400).json({
|
|
4791
|
+
error: detail.message,
|
|
4792
|
+
code: detail.code ?? 'AGENT_PLUGIN_UPDATE_FAILED',
|
|
4793
|
+
migration: detail.migration,
|
|
4794
|
+
});
|
|
4795
|
+
}
|
|
4796
|
+
});
|
|
4616
4797
|
router.post('/agent-plugins', async (req, res) => {
|
|
4617
4798
|
const manifest = req.body;
|
|
4618
4799
|
if (!manifest || typeof manifest !== 'object') {
|
|
@@ -4638,10 +4819,7 @@ router.post('/agent-plugins', async (req, res) => {
|
|
|
4638
4819
|
return;
|
|
4639
4820
|
}
|
|
4640
4821
|
const dir = savePlugin(validation.manifest);
|
|
4641
|
-
|
|
4642
|
-
clearPluginAgents();
|
|
4643
|
-
const loaded = loadPlugins();
|
|
4644
|
-
registerPluginAgents(loaded.plugins);
|
|
4822
|
+
reloadAgentPlugins();
|
|
4645
4823
|
res.json({ slug: validation.manifest.slug, dir, state: 'created' });
|
|
4646
4824
|
}
|
|
4647
4825
|
catch (error) {
|
|
@@ -5126,7 +5304,7 @@ router.post('/:sessionId/input', express.text({ type: '*/*' }), (req, res) => {
|
|
|
5126
5304
|
* bracketed paste. Refused while an agent turn is in flight — pasting a
|
|
5127
5305
|
* resume into a busy TUI would corrupt its input.
|
|
5128
5306
|
*/
|
|
5129
|
-
router.post('/:sessionId/agent-resume', (req, res) => {
|
|
5307
|
+
router.post('/:sessionId/agent-resume', async (req, res) => {
|
|
5130
5308
|
const { sessionId } = req.params;
|
|
5131
5309
|
const session = terminalSessions.get(sessionId);
|
|
5132
5310
|
if (!session) {
|
|
@@ -5136,14 +5314,30 @@ router.post('/:sessionId/agent-resume', (req, res) => {
|
|
|
5136
5314
|
if (session.agent && live && (live.status === 'working' || live.status === 'waiting')) {
|
|
5137
5315
|
return res.status(409).json({ error: 'agent is busy; wait for the turn to finish' });
|
|
5138
5316
|
}
|
|
5139
|
-
const
|
|
5140
|
-
if (!
|
|
5317
|
+
const target = resolveAgentResumeTarget(sessionId, session);
|
|
5318
|
+
if (!target) {
|
|
5141
5319
|
return res.status(404).json({ error: 'no resumable agent session for this pane' });
|
|
5142
5320
|
}
|
|
5321
|
+
const activeOwner = findActiveAgentResumeOwner(sessionId, target);
|
|
5322
|
+
if (activeOwner) {
|
|
5323
|
+
return res.status(409).json({
|
|
5324
|
+
error: 'this agent session is already open in another terminal',
|
|
5325
|
+
code: 'AGENT_SESSION_ACTIVE_ELSEWHERE',
|
|
5326
|
+
activeBackendSessionId: activeOwner,
|
|
5327
|
+
});
|
|
5328
|
+
}
|
|
5329
|
+
const activeWriterPid = await findExternalCodexWriter(target);
|
|
5330
|
+
if (activeWriterPid) {
|
|
5331
|
+
return res.status(409).json({
|
|
5332
|
+
error: 'this Codex thread already has an active writer',
|
|
5333
|
+
code: 'AGENT_SESSION_ACTIVE_ELSEWHERE',
|
|
5334
|
+
activeWriterPid,
|
|
5335
|
+
});
|
|
5336
|
+
}
|
|
5143
5337
|
try {
|
|
5144
|
-
session.ptyProcess.write(buildBracketedSubmitBytes(command));
|
|
5338
|
+
session.ptyProcess.write(buildBracketedSubmitBytes(target.command));
|
|
5145
5339
|
session.lastActivity = Date.now();
|
|
5146
|
-
res.json({ success: true, command });
|
|
5340
|
+
res.json({ success: true, command: target.command });
|
|
5147
5341
|
}
|
|
5148
5342
|
catch (error) {
|
|
5149
5343
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -5151,16 +5345,25 @@ router.post('/:sessionId/agent-resume', (req, res) => {
|
|
|
5151
5345
|
}
|
|
5152
5346
|
});
|
|
5153
5347
|
/** Whether this pane has a resumable agent conversation (for the UI affordance). */
|
|
5154
|
-
router.get('/:sessionId/agent-resume', (req, res) => {
|
|
5348
|
+
router.get('/:sessionId/agent-resume', async (req, res) => {
|
|
5155
5349
|
const { sessionId } = req.params;
|
|
5156
5350
|
const session = terminalSessions.get(sessionId);
|
|
5157
5351
|
if (!session) {
|
|
5158
5352
|
return res.status(404).json({ error: 'Terminal session not found' });
|
|
5159
5353
|
}
|
|
5160
|
-
const
|
|
5354
|
+
const target = resolveAgentResumeTarget(sessionId, session);
|
|
5161
5355
|
const busy = !!(session.agent && session.agentSession
|
|
5162
5356
|
&& (session.agentSession.status === 'working' || session.agentSession.status === 'waiting'));
|
|
5163
|
-
|
|
5357
|
+
const activeBackendSessionId = target ? findActiveAgentResumeOwner(sessionId, target) : null;
|
|
5358
|
+
const activeWriterPid = target && !activeBackendSessionId ? await findExternalCodexWriter(target) : null;
|
|
5359
|
+
res.json({
|
|
5360
|
+
available: target !== null && activeBackendSessionId === null && activeWriterPid === null,
|
|
5361
|
+
command: target?.command ?? null,
|
|
5362
|
+
busy,
|
|
5363
|
+
conflict: activeBackendSessionId || activeWriterPid ? 'active-elsewhere' : null,
|
|
5364
|
+
activeBackendSessionId,
|
|
5365
|
+
activeWriterPid,
|
|
5366
|
+
});
|
|
5164
5367
|
});
|
|
5165
5368
|
router.post('/:sessionId/resize', async (req, res) => {
|
|
5166
5369
|
const { sessionId } = req.params;
|
|
@@ -62,8 +62,11 @@ export class CsrfProtection {
|
|
|
62
62
|
/**
|
|
63
63
|
* Express中间件:验证CSRF令牌
|
|
64
64
|
*/
|
|
65
|
-
verifyMiddleware() {
|
|
65
|
+
verifyMiddleware(options) {
|
|
66
66
|
return (req, res, next) => {
|
|
67
|
+
if (options?.bypass?.(req)) {
|
|
68
|
+
return next();
|
|
69
|
+
}
|
|
67
70
|
// 跳过安全检查和GET/HEAD/OPTIONS请求
|
|
68
71
|
const safeMethods = ['GET', 'HEAD', 'OPTIONS'];
|
|
69
72
|
if (safeMethods.includes(req.method)) {
|
|
@@ -79,6 +79,12 @@ function normalizeSettings(value) {
|
|
|
79
79
|
&& typeof entry[1] === 'string'
|
|
80
80
|
&& /^[a-zA-Z0-9][a-zA-Z0-9._:-]{0,119}$/.test(entry[1]))))
|
|
81
81
|
: {},
|
|
82
|
+
autoRenameIntervalMinutes: typeof raw.autoRenameIntervalMinutes === 'number'
|
|
83
|
+
&& Number.isInteger(raw.autoRenameIntervalMinutes)
|
|
84
|
+
&& raw.autoRenameIntervalMinutes >= 5
|
|
85
|
+
&& raw.autoRenameIntervalMinutes <= 1440
|
|
86
|
+
? raw.autoRenameIntervalMinutes
|
|
87
|
+
: 10,
|
|
82
88
|
updatedAt: typeof raw.updatedAt === 'number' ? raw.updatedAt : Date.now(),
|
|
83
89
|
};
|
|
84
90
|
}
|
|
@@ -218,6 +224,14 @@ export function setAutoRenameNamerSetting(namer) {
|
|
|
218
224
|
export function getAutoRenameModelsSetting() {
|
|
219
225
|
return { ...loadSettings().autoRenameModels };
|
|
220
226
|
}
|
|
227
|
+
export function getAutoRenameIntervalMinutesSetting() {
|
|
228
|
+
return loadSettings().autoRenameIntervalMinutes;
|
|
229
|
+
}
|
|
230
|
+
export function setAutoRenameIntervalMinutesSetting(minutes) {
|
|
231
|
+
return updateSettings((settings) => {
|
|
232
|
+
settings.autoRenameIntervalMinutes = Math.max(5, Math.min(1440, Math.round(minutes)));
|
|
233
|
+
});
|
|
234
|
+
}
|
|
221
235
|
export function setAutoRenameModelsSetting(models) {
|
|
222
236
|
return updateSettings((settings) => {
|
|
223
237
|
settings.autoRenameModels = Object.fromEntries(Object.entries(models)
|
package/package.json
CHANGED
package/runtime-manifest.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"packageName": "termdock",
|
|
4
|
-
"version": "1.4.
|
|
4
|
+
"version": "1.4.72",
|
|
5
5
|
"runtimeProtocolVersion": 1,
|
|
6
6
|
"minimumDesktopVersion": "1.4.46",
|
|
7
7
|
"nodeMajor": 22,
|
|
8
8
|
"dependencyHash": "sha256-HxC6p3b4kFesefw+vmNYJtPtdIKJaZR1jFNb7DRJqjU=",
|
|
9
|
-
"serverBundleHash": "sha256-
|
|
9
|
+
"serverBundleHash": "sha256-4Ti+r7BiQtaMfPn2SORjiaLhcxgBT7xkGbas+Dsh6Zo=",
|
|
10
10
|
"entrypoint": "dist/server/cli.js",
|
|
11
11
|
"clientEntrypoint": "dist/client/index.html"
|
|
12
12
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{U as a,C as n}from"./mermaid.core-CsAzdiCU.js";const t=(r,o)=>a.lang.round(n.parse(r)[o]);export{t as c};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,a as s,c as t,C as e}from"./chunk-V7JOEXUC-CSZrnmp_.js";import{b as i}from"./mermaid.core-CsAzdiCU.js";import"./chunk-5VM5RSS4-CjbUr-ko.js";import"./chunk-XXDRQBXY-CcZZlCik.js";import"./chunk-VR4S4FIN-BB-7zz-9.js";import"./chunk-32BRIVSS-CrqR5rvl.js";import"./purify.es-CC4Brkr_.js";import"./index-D5PKdbhW.js";import"./react-XDCjal2T.js";import"./dnd-BCFAPC0L.js";import"./terminal-C7X98Amf.js";var g={parser:t,get db(){return new e},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{g as diagram};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,a as s,c as t,C as e}from"./chunk-V7JOEXUC-CSZrnmp_.js";import{b as i}from"./mermaid.core-CsAzdiCU.js";import"./chunk-5VM5RSS4-CjbUr-ko.js";import"./chunk-XXDRQBXY-CcZZlCik.js";import"./chunk-VR4S4FIN-BB-7zz-9.js";import"./chunk-32BRIVSS-CrqR5rvl.js";import"./purify.es-CC4Brkr_.js";import"./index-D5PKdbhW.js";import"./react-XDCjal2T.js";import"./dnd-BCFAPC0L.js";import"./terminal-C7X98Amf.js";var g={parser:t,get db(){return new e},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{g as diagram};
|