termdock 1.4.71 → 1.4.73
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 +59 -0
- package/dist/client/assets/{ModelPreview-CG6vLwyw.js → ModelPreview-BYS46cJp.js} +1 -1
- package/dist/client/assets/{abnfDiagram-VRR7QNED-CrXEZLWX.js → abnfDiagram-VRR7QNED-Df5iizBM.js} +1 -1
- package/dist/client/assets/{arc-DbKGR7Ex.js → arc-BRLIrHaj.js} +1 -1
- package/dist/client/assets/{architectureDiagram-ZJ3FMSHR-PF5BoUbI.js → architectureDiagram-ZJ3FMSHR-BmIKhrrq.js} +1 -1
- package/dist/client/assets/{blockDiagram-677ZJIJ3-D1gRzVWB.js → blockDiagram-677ZJIJ3-PBtq2qLG.js} +1 -1
- package/dist/client/assets/{c4Diagram-LMCZKHZV-CwvOLync.js → c4Diagram-LMCZKHZV-6pevynP4.js} +1 -1
- package/dist/client/assets/channel-CfecnPVa.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-BeRdP-xP.js → chunk-2Q5K7J3B-78EZViva.js} +1 -1
- package/dist/client/assets/{chunk-32BRIVSS-CrqR5rvl.js → chunk-32BRIVSS-CekczK5l.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-CjbUr-ko.js → chunk-5VM5RSS4-fMhlGbVA.js} +1 -1
- package/dist/client/assets/{chunk-EX3LRPZG-BVEk3Wma.js → chunk-EX3LRPZG-DOBewV4Z.js} +1 -1
- package/dist/client/assets/{chunk-JWPE2WC7-WitJe8kR.js → chunk-JWPE2WC7-rtk-mGC7.js} +1 -1
- package/dist/client/assets/{chunk-MOJQB5TN-BCewlLYk.js → chunk-MOJQB5TN-CWUZocq_.js} +1 -1
- package/dist/client/assets/{chunk-RYQCIY6F-CoP0AA4G.js → chunk-RYQCIY6F-DcHAjeyV.js} +1 -1
- package/dist/client/assets/{chunk-V7JOEXUC-CSZrnmp_.js → chunk-V7JOEXUC-C2paDSKz.js} +1 -1
- package/dist/client/assets/{chunk-VR4S4FIN-BB-7zz-9.js → chunk-VR4S4FIN-Cq0ZrFUq.js} +1 -1
- package/dist/client/assets/{chunk-XXDRQBXY-CcZZlCik.js → chunk-XXDRQBXY-BboySYmP.js} +1 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-CFpMarVm.js +1 -0
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-CFpMarVm.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-DcmzUDwh.js → cose-bilkent-JH36ORCC-CMnHIr6y.js} +1 -1
- package/dist/client/assets/{cynefin-VYW2F7L2-C1XlDVoJ.js → cynefin-VYW2F7L2-BpOtLuYh.js} +1 -1
- package/dist/client/assets/{cynefinDiagram-TSTJHNR4-zY6Aet2S.js → cynefinDiagram-TSTJHNR4-CmrMqA7z.js} +1 -1
- package/dist/client/assets/{dagre-VKFMJZFB-DqGTi6zR.js → dagre-VKFMJZFB-iXOV-rm7.js} +1 -1
- package/dist/client/assets/{diagram-FQU43EPY-DStcsU7S.js → diagram-FQU43EPY-D5KagnmX.js} +1 -1
- package/dist/client/assets/{diagram-G47NLZAW-Cs6IMNip.js → diagram-G47NLZAW-CglgkfyY.js} +1 -1
- package/dist/client/assets/{diagram-NH7WQ7WH-yA2bpWN7.js → diagram-NH7WQ7WH-Dos-y1pl.js} +1 -1
- package/dist/client/assets/{diagram-OA4YK3LP-Dzf9M0PI.js → diagram-OA4YK3LP-Cw6yJqFX.js} +1 -1
- package/dist/client/assets/{diagram-WEI45ONY-D75lpQKA.js → diagram-WEI45ONY-C_MHHYRA.js} +1 -1
- package/dist/client/assets/{ebnfDiagram-CCIWWBDH-CkQvIpWy.js → ebnfDiagram-CCIWWBDH-BB7x8X2l.js} +1 -1
- package/dist/client/assets/{erDiagram-Q63AITRT-Ca00Vjxz.js → erDiagram-Q63AITRT-BznrrU3P.js} +1 -1
- package/dist/client/assets/{flowDiagram-23GEKE2U-CGrnSTEp.js → flowDiagram-23GEKE2U-CMzPvwGa.js} +1 -1
- package/dist/client/assets/{ganttDiagram-NO4QXBWP-0SHfOIP4.js → ganttDiagram-NO4QXBWP-Dc9JCHv3.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-IHSO6WYX-BeigYWvs.js → gitGraphDiagram-IHSO6WYX-WlPpmZQm.js} +1 -1
- package/dist/client/assets/index-wX3Y6Ks7.js +451 -0
- package/dist/client/assets/{infoDiagram-FWYZ7A6U-CiY1-xjN.js → infoDiagram-FWYZ7A6U-DC6PBJiN.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-FXEZZL3T-CmtDjUm_.js → ishikawaDiagram-FXEZZL3T-DPfxPu0W.js} +1 -1
- package/dist/client/assets/{journeyDiagram-5HDEW3XC-B7qOr9LY.js → journeyDiagram-5HDEW3XC-aA2ovr_f.js} +1 -1
- package/dist/client/assets/{kanban-definition-HUTT4EX6-DO2P2Um3.js → kanban-definition-HUTT4EX6-_T30f7Or.js} +1 -1
- package/dist/client/assets/{linear-B832hJt0.js → linear-DRZcO7_m.js} +1 -1
- package/dist/client/assets/{mermaid.core-CsAzdiCU.js → mermaid.core-BGXjYfdb.js} +4 -4
- package/dist/client/assets/{mindmap-definition-LN4V7U3C-HI__vXhQ.js → mindmap-definition-LN4V7U3C-Bv0lhpqs.js} +1 -1
- package/dist/client/assets/{pegDiagram-2B236MQR--rZIwjXq.js → pegDiagram-2B236MQR-D-EIIOZf.js} +1 -1
- package/dist/client/assets/{pieDiagram-ENE6RG2P-D9jPf5x9.js → pieDiagram-ENE6RG2P-CATAcPgl.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-ABIIQ3AL-DTCP5ji2.js → quadrantDiagram-ABIIQ3AL-2tglHVSk.js} +1 -1
- package/dist/client/assets/{railroadDiagram-RFXS5EU6-D2Gspvrl.js → railroadDiagram-RFXS5EU6-BMKC-e8R.js} +1 -1
- package/dist/client/assets/{requirementDiagram-TGXJPOKE-DEJC02IW.js → requirementDiagram-TGXJPOKE-B5J5_wFW.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-HTMAVEWB-opOhKPap.js → sankeyDiagram-HTMAVEWB-Djlm71ae.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-DBY2YBRQ-_sz2CRAG.js → sequenceDiagram-DBY2YBRQ-BtR8OPSJ.js} +1 -1
- package/dist/client/assets/{sizeCapture-X5ZJPWSS-D0Q2QkYF.js → sizeCapture-X5ZJPWSS-CDelD9h_.js} +1 -1
- package/dist/client/assets/{stateDiagram-2N3HPSRC-VgeSQTtW.js → stateDiagram-2N3HPSRC-C50kGru1.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-gIr1L4WZ.js +1 -0
- package/dist/client/assets/{swimlanes-5IMT3BWC-ClvBvSl0.js → swimlanes-5IMT3BWC-COHxl86-.js} +2 -2
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-COE4p0kX.js +8 -0
- package/dist/client/assets/{timeline-definition-FHXFAJF6-BXNsrECV.js → timeline-definition-FHXFAJF6-br-VI8Fd.js} +1 -1
- package/dist/client/assets/{vennDiagram-L72KCM5P-BgwiqZd-.js → vennDiagram-L72KCM5P-zwMtEv81.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-EHGQE667-DiqN5ERx.js → wardleyDiagram-EHGQE667-_C8v4nnr.js} +1 -1
- package/dist/client/assets/{xychartDiagram-FW5EYKEG-DQjFVqC6.js → xychartDiagram-FW5EYKEG-CSU2R6pt.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 +139 -20
- package/dist/server/agent/titleNamerCatalog.js +235 -40
- package/dist/server/cli.js +277 -17
- package/dist/server/entry.js +11 -2
- package/dist/server/routes/terminal.js +241 -30
- 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,13 +23,14 @@ 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 { getTitleNamerCatalog, invalidateTitleNamerCatalog } from '../agent/titleNamerCatalog.js';
|
|
32
|
+
import { AUTO_TITLE_MIN_CONTEXT_CHARS, cleanTerminalContext, generateAgentTitle, isNewAgentSessionId, isAutoTitleReevaluationDue, shouldReplaceAutoTitle, } from '../agent/autoTitle.js';
|
|
33
|
+
import { getTitleNamerCatalog, invalidateTitleNamerCatalog, probePluginTitleNamer } from '../agent/titleNamerCatalog.js';
|
|
33
34
|
const router = express.Router();
|
|
34
35
|
const execFileAsync = promisify(execFile);
|
|
35
36
|
const TERMDOCK_DIR = `${os.homedir()}/.termdock`;
|
|
@@ -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,140 @@ 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/doctor', async (req, res) => {
|
|
4723
|
+
const result = await probePluginTitleNamer(req.params.slug);
|
|
4724
|
+
if (!result) {
|
|
4725
|
+
res.status(404).json({ error: `Plugin "${req.params.slug}" not found`, code: 'AGENT_PLUGIN_NOT_FOUND' });
|
|
4726
|
+
return;
|
|
4727
|
+
}
|
|
4728
|
+
res.json(result);
|
|
4729
|
+
});
|
|
4730
|
+
router.post('/agent-plugins/:slug/update', async (req, res) => {
|
|
4731
|
+
const slug = req.params.slug;
|
|
4732
|
+
const plugin = loadPlugins().plugins.find((entry) => entry.manifest.slug === slug);
|
|
4733
|
+
if (!plugin) {
|
|
4734
|
+
res.status(404).json({ error: `Plugin "${slug}" not found` });
|
|
4735
|
+
return;
|
|
4736
|
+
}
|
|
4737
|
+
if (!plugin.source?.source || plugin.source.type === 'manifest') {
|
|
4738
|
+
res.status(400).json({ error: 'This plugin has no update source', code: 'AGENT_PLUGIN_UPDATE_UNSUPPORTED' });
|
|
4739
|
+
return;
|
|
4740
|
+
}
|
|
4741
|
+
let prepared = null;
|
|
4742
|
+
let oldHooksRemoved = false;
|
|
4743
|
+
let committed = false;
|
|
4744
|
+
try {
|
|
4745
|
+
// Download and validate the complete replacement before touching the live
|
|
4746
|
+
// package or its currently installed hooks.
|
|
4747
|
+
prepared = await preparePluginPackage(plugin.source.source, slug);
|
|
4748
|
+
const hooksChanged = JSON.stringify(plugin.manifest.hooks ?? null) !== JSON.stringify(prepared.manifest.hooks ?? null);
|
|
4749
|
+
const titleCapabilityChanged = JSON.stringify(plugin.manifest.titleNamer ?? null) !== JSON.stringify(prepared.manifest.titleNamer ?? null);
|
|
4750
|
+
const hookState = (await listAllHookAgents()).find((entry) => entry.slug === slug)?.state;
|
|
4751
|
+
const restoreHooks = hookState === 'installed' || hookState === 'outdated';
|
|
4752
|
+
if (restoreHooks && plugin.manifest.hooks) {
|
|
4753
|
+
await uninstallHooksForSlug(slug);
|
|
4754
|
+
oldHooksRemoved = true;
|
|
4755
|
+
}
|
|
4756
|
+
const dir = await commitPreparedPlugin(prepared, true);
|
|
4757
|
+
prepared = null;
|
|
4758
|
+
committed = true;
|
|
4759
|
+
reloadAgentPlugins();
|
|
4760
|
+
let hookWarning = null;
|
|
4761
|
+
if (restoreHooks && hooksChanged) {
|
|
4762
|
+
hookWarning = 'Hook declarations changed during update. Review them, then install hooks again.';
|
|
4763
|
+
}
|
|
4764
|
+
else if (restoreHooks) {
|
|
4765
|
+
try {
|
|
4766
|
+
await installHooksForSlug(slug);
|
|
4767
|
+
}
|
|
4768
|
+
catch (error) {
|
|
4769
|
+
hookWarning = error.message;
|
|
4770
|
+
}
|
|
4771
|
+
}
|
|
4772
|
+
let titleWarning = null;
|
|
4773
|
+
if (titleCapabilityChanged) {
|
|
4774
|
+
setAutoRenameAgentsSetting(getAutoRenameAgentsSetting().filter((entry) => entry !== slug));
|
|
4775
|
+
if (getAutoRenameNamerSetting() === slug)
|
|
4776
|
+
setAutoRenameNamerSetting('auto');
|
|
4777
|
+
const models = getAutoRenameModelsSetting();
|
|
4778
|
+
delete models[slug];
|
|
4779
|
+
setAutoRenameModelsSetting(models);
|
|
4780
|
+
invalidateTitleNamerCatalog();
|
|
4781
|
+
titleWarning = 'Title command declarations changed during update. Automatic titles for this plugin were disabled; review and enable them again.';
|
|
4782
|
+
}
|
|
4783
|
+
res.json({
|
|
4784
|
+
slug,
|
|
4785
|
+
dir,
|
|
4786
|
+
state: 'updated',
|
|
4787
|
+
source: loadPlugins().plugins.find((entry) => entry.manifest.slug === slug)?.source,
|
|
4788
|
+
hookWarning,
|
|
4789
|
+
titleWarning,
|
|
4790
|
+
});
|
|
4791
|
+
}
|
|
4792
|
+
catch (error) {
|
|
4793
|
+
if (prepared)
|
|
4794
|
+
await prepared.cleanup().catch(() => undefined);
|
|
4795
|
+
if (oldHooksRemoved && !committed)
|
|
4796
|
+
await installHooksForSlug(slug).catch(() => undefined);
|
|
4797
|
+
const detail = error;
|
|
4798
|
+
res.status(400).json({
|
|
4799
|
+
error: detail.message,
|
|
4800
|
+
code: detail.code ?? 'AGENT_PLUGIN_UPDATE_FAILED',
|
|
4801
|
+
migration: detail.migration,
|
|
4802
|
+
});
|
|
4803
|
+
}
|
|
4804
|
+
});
|
|
4616
4805
|
router.post('/agent-plugins', async (req, res) => {
|
|
4617
4806
|
const manifest = req.body;
|
|
4618
4807
|
if (!manifest || typeof manifest !== 'object') {
|
|
@@ -4638,10 +4827,7 @@ router.post('/agent-plugins', async (req, res) => {
|
|
|
4638
4827
|
return;
|
|
4639
4828
|
}
|
|
4640
4829
|
const dir = savePlugin(validation.manifest);
|
|
4641
|
-
|
|
4642
|
-
clearPluginAgents();
|
|
4643
|
-
const loaded = loadPlugins();
|
|
4644
|
-
registerPluginAgents(loaded.plugins);
|
|
4830
|
+
reloadAgentPlugins();
|
|
4645
4831
|
res.json({ slug: validation.manifest.slug, dir, state: 'created' });
|
|
4646
4832
|
}
|
|
4647
4833
|
catch (error) {
|
|
@@ -5126,7 +5312,7 @@ router.post('/:sessionId/input', express.text({ type: '*/*' }), (req, res) => {
|
|
|
5126
5312
|
* bracketed paste. Refused while an agent turn is in flight — pasting a
|
|
5127
5313
|
* resume into a busy TUI would corrupt its input.
|
|
5128
5314
|
*/
|
|
5129
|
-
router.post('/:sessionId/agent-resume', (req, res) => {
|
|
5315
|
+
router.post('/:sessionId/agent-resume', async (req, res) => {
|
|
5130
5316
|
const { sessionId } = req.params;
|
|
5131
5317
|
const session = terminalSessions.get(sessionId);
|
|
5132
5318
|
if (!session) {
|
|
@@ -5136,14 +5322,30 @@ router.post('/:sessionId/agent-resume', (req, res) => {
|
|
|
5136
5322
|
if (session.agent && live && (live.status === 'working' || live.status === 'waiting')) {
|
|
5137
5323
|
return res.status(409).json({ error: 'agent is busy; wait for the turn to finish' });
|
|
5138
5324
|
}
|
|
5139
|
-
const
|
|
5140
|
-
if (!
|
|
5325
|
+
const target = resolveAgentResumeTarget(sessionId, session);
|
|
5326
|
+
if (!target) {
|
|
5141
5327
|
return res.status(404).json({ error: 'no resumable agent session for this pane' });
|
|
5142
5328
|
}
|
|
5329
|
+
const activeOwner = findActiveAgentResumeOwner(sessionId, target);
|
|
5330
|
+
if (activeOwner) {
|
|
5331
|
+
return res.status(409).json({
|
|
5332
|
+
error: 'this agent session is already open in another terminal',
|
|
5333
|
+
code: 'AGENT_SESSION_ACTIVE_ELSEWHERE',
|
|
5334
|
+
activeBackendSessionId: activeOwner,
|
|
5335
|
+
});
|
|
5336
|
+
}
|
|
5337
|
+
const activeWriterPid = await findExternalCodexWriter(target);
|
|
5338
|
+
if (activeWriterPid) {
|
|
5339
|
+
return res.status(409).json({
|
|
5340
|
+
error: 'this Codex thread already has an active writer',
|
|
5341
|
+
code: 'AGENT_SESSION_ACTIVE_ELSEWHERE',
|
|
5342
|
+
activeWriterPid,
|
|
5343
|
+
});
|
|
5344
|
+
}
|
|
5143
5345
|
try {
|
|
5144
|
-
session.ptyProcess.write(buildBracketedSubmitBytes(command));
|
|
5346
|
+
session.ptyProcess.write(buildBracketedSubmitBytes(target.command));
|
|
5145
5347
|
session.lastActivity = Date.now();
|
|
5146
|
-
res.json({ success: true, command });
|
|
5348
|
+
res.json({ success: true, command: target.command });
|
|
5147
5349
|
}
|
|
5148
5350
|
catch (error) {
|
|
5149
5351
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -5151,16 +5353,25 @@ router.post('/:sessionId/agent-resume', (req, res) => {
|
|
|
5151
5353
|
}
|
|
5152
5354
|
});
|
|
5153
5355
|
/** Whether this pane has a resumable agent conversation (for the UI affordance). */
|
|
5154
|
-
router.get('/:sessionId/agent-resume', (req, res) => {
|
|
5356
|
+
router.get('/:sessionId/agent-resume', async (req, res) => {
|
|
5155
5357
|
const { sessionId } = req.params;
|
|
5156
5358
|
const session = terminalSessions.get(sessionId);
|
|
5157
5359
|
if (!session) {
|
|
5158
5360
|
return res.status(404).json({ error: 'Terminal session not found' });
|
|
5159
5361
|
}
|
|
5160
|
-
const
|
|
5362
|
+
const target = resolveAgentResumeTarget(sessionId, session);
|
|
5161
5363
|
const busy = !!(session.agent && session.agentSession
|
|
5162
5364
|
&& (session.agentSession.status === 'working' || session.agentSession.status === 'waiting'));
|
|
5163
|
-
|
|
5365
|
+
const activeBackendSessionId = target ? findActiveAgentResumeOwner(sessionId, target) : null;
|
|
5366
|
+
const activeWriterPid = target && !activeBackendSessionId ? await findExternalCodexWriter(target) : null;
|
|
5367
|
+
res.json({
|
|
5368
|
+
available: target !== null && activeBackendSessionId === null && activeWriterPid === null,
|
|
5369
|
+
command: target?.command ?? null,
|
|
5370
|
+
busy,
|
|
5371
|
+
conflict: activeBackendSessionId || activeWriterPid ? 'active-elsewhere' : null,
|
|
5372
|
+
activeBackendSessionId,
|
|
5373
|
+
activeWriterPid,
|
|
5374
|
+
});
|
|
5164
5375
|
});
|
|
5165
5376
|
router.post('/:sessionId/resize', async (req, res) => {
|
|
5166
5377
|
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.73",
|
|
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-xzvKVLcvFdBQsk4jGML445ipTKOjQeaGWTmNMudp3p0=",
|
|
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};
|