termdock 1.4.69 → 1.4.70
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/.desktop-dist/main.js +254 -49
- package/README.md +6 -0
- package/dist/client/assets/{ModelPreview-Bk8GM7G-.js → ModelPreview-Cs14IQuQ.js} +1 -1
- package/dist/client/assets/{abnfDiagram-VRR7QNED-BYiQXGYk.js → abnfDiagram-VRR7QNED-DuDfdv-6.js} +1 -1
- package/dist/client/assets/{arc-jlT_jmoZ.js → arc-CSYzGV_2.js} +1 -1
- package/dist/client/assets/{architectureDiagram-ZJ3FMSHR-D1qZ875Q.js → architectureDiagram-ZJ3FMSHR-YiZ0HXNi.js} +1 -1
- package/dist/client/assets/{blockDiagram-677ZJIJ3-qkgn2Ysc.js → blockDiagram-677ZJIJ3-IaQMzmYv.js} +1 -1
- package/dist/client/assets/{c4Diagram-LMCZKHZV-JgcH49Pf.js → c4Diagram-LMCZKHZV-BwyuIPRL.js} +1 -1
- package/dist/client/assets/channel-BotxtiNE.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-E-3iflEy.js → chunk-2Q5K7J3B-BtHQYLBT.js} +1 -1
- package/dist/client/assets/{chunk-32BRIVSS-mM01y3kF.js → chunk-32BRIVSS-CXrK07sW.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-GO8Muo5l.js → chunk-5VM5RSS4-BfBshn11.js} +1 -1
- package/dist/client/assets/{chunk-EX3LRPZG-BUSDluH0.js → chunk-EX3LRPZG-BJbilNaf.js} +1 -1
- package/dist/client/assets/{chunk-JWPE2WC7-obSL6EzJ.js → chunk-JWPE2WC7-wgcmSRlL.js} +1 -1
- package/dist/client/assets/{chunk-MOJQB5TN-zEktbtAG.js → chunk-MOJQB5TN-8VZVUeDI.js} +1 -1
- package/dist/client/assets/{chunk-RYQCIY6F-kH7H8XgQ.js → chunk-RYQCIY6F-B900YBFa.js} +1 -1
- package/dist/client/assets/{chunk-V7JOEXUC-BqDFpHKN.js → chunk-V7JOEXUC-BK4_RLz-.js} +1 -1
- package/dist/client/assets/{chunk-VR4S4FIN-BGZ7kCIG.js → chunk-VR4S4FIN-Kj5_ycjB.js} +1 -1
- package/dist/client/assets/{chunk-XXDRQBXY-D-TatWXe.js → chunk-XXDRQBXY-BolvPLRz.js} +1 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-CLWlbTSC.js +1 -0
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-CLWlbTSC.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-CfWyrViz.js → cose-bilkent-JH36ORCC-9_9_SLqv.js} +1 -1
- package/dist/client/assets/{cynefin-VYW2F7L2-BFdZ3FcE.js → cynefin-VYW2F7L2-yEpoi3vG.js} +1 -1
- package/dist/client/assets/{cynefinDiagram-TSTJHNR4-iAfCVoFU.js → cynefinDiagram-TSTJHNR4-Dy-gvup_.js} +1 -1
- package/dist/client/assets/{dagre-VKFMJZFB-CZhh0hrr.js → dagre-VKFMJZFB-CNjJ5pRK.js} +1 -1
- package/dist/client/assets/{diagram-FQU43EPY-BqUK2OmV.js → diagram-FQU43EPY-uZ73Rj3T.js} +1 -1
- package/dist/client/assets/{diagram-G47NLZAW-vC0pSQYC.js → diagram-G47NLZAW-Cxt7TYZT.js} +1 -1
- package/dist/client/assets/{diagram-NH7WQ7WH-Dqc5oxed.js → diagram-NH7WQ7WH-B4TKinuZ.js} +1 -1
- package/dist/client/assets/{diagram-OA4YK3LP-DIIXckFE.js → diagram-OA4YK3LP-CGSuenHU.js} +1 -1
- package/dist/client/assets/{diagram-WEI45ONY-DR9_cFeh.js → diagram-WEI45ONY-DqqzDaUZ.js} +1 -1
- package/dist/client/assets/{ebnfDiagram-CCIWWBDH-21OO0OXC.js → ebnfDiagram-CCIWWBDH-B2SgXL5w.js} +1 -1
- package/dist/client/assets/{erDiagram-Q63AITRT-YKnVG3az.js → erDiagram-Q63AITRT-D-ibkr8y.js} +1 -1
- package/dist/client/assets/{flowDiagram-23GEKE2U-Cm7TGErF.js → flowDiagram-23GEKE2U-BPeyMqr-.js} +1 -1
- package/dist/client/assets/{ganttDiagram-NO4QXBWP-D_76Effz.js → ganttDiagram-NO4QXBWP-DcEqEzzu.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-IHSO6WYX-CC1ysmVD.js → gitGraphDiagram-IHSO6WYX-DTZ6UCQY.js} +1 -1
- package/dist/client/assets/index-DhfIiBLW.css +1 -0
- package/dist/client/assets/index-ivnGnbVg.js +451 -0
- package/dist/client/assets/{infoDiagram-FWYZ7A6U-BbgpmhBz.js → infoDiagram-FWYZ7A6U-BHrzVXeX.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-FXEZZL3T-C94bY_je.js → ishikawaDiagram-FXEZZL3T-CwuoQJku.js} +1 -1
- package/dist/client/assets/{journeyDiagram-5HDEW3XC-BQ0iFRxs.js → journeyDiagram-5HDEW3XC-ZEcy0rDZ.js} +1 -1
- package/dist/client/assets/{kanban-definition-HUTT4EX6-BtAexXPo.js → kanban-definition-HUTT4EX6-BDBawNbr.js} +1 -1
- package/dist/client/assets/{linear-C2HnIdOK.js → linear-smYQWzDV.js} +1 -1
- package/dist/client/assets/{mermaid.core-BbG4sJBF.js → mermaid.core-eJs7Ouvw.js} +4 -4
- package/dist/client/assets/{mindmap-definition-LN4V7U3C-B-uvViws.js → mindmap-definition-LN4V7U3C-BK1IZ9zg.js} +1 -1
- package/dist/client/assets/{pegDiagram-2B236MQR-DHYcR6tE.js → pegDiagram-2B236MQR-B3-mVNgX.js} +1 -1
- package/dist/client/assets/{pieDiagram-ENE6RG2P-DEE9cD6M.js → pieDiagram-ENE6RG2P-Bkeeyx5l.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-ABIIQ3AL-DMQP5jG3.js → quadrantDiagram-ABIIQ3AL-DouIklEO.js} +1 -1
- package/dist/client/assets/{railroadDiagram-RFXS5EU6-B-4WDTwc.js → railroadDiagram-RFXS5EU6-BB_jXQd2.js} +1 -1
- package/dist/client/assets/{requirementDiagram-TGXJPOKE-C2xw_q-R.js → requirementDiagram-TGXJPOKE-u56IHfiR.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-HTMAVEWB-wq0BPqhc.js → sankeyDiagram-HTMAVEWB-eDi2cs5E.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-DBY2YBRQ-BqpM0A9d.js → sequenceDiagram-DBY2YBRQ-DnAA2xYa.js} +1 -1
- package/dist/client/assets/{sizeCapture-X5ZJPWSS-Ba2xxyp_.js → sizeCapture-X5ZJPWSS-BogPui79.js} +1 -1
- package/dist/client/assets/{stateDiagram-2N3HPSRC-agPx5qAN.js → stateDiagram-2N3HPSRC--EeXDy2M.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-Dc-3HKTs.js +1 -0
- package/dist/client/assets/{swimlanes-5IMT3BWC-Cy6Lzj_L.js → swimlanes-5IMT3BWC-DZAw7-kY.js} +2 -2
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-CI1Wlr0V.js +8 -0
- package/dist/client/assets/{timeline-definition-FHXFAJF6-LBrVfbdp.js → timeline-definition-FHXFAJF6-DkcnpB-Y.js} +1 -1
- package/dist/client/assets/{vennDiagram-L72KCM5P-BiYIV7mX.js → vennDiagram-L72KCM5P-BStcDGEf.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-EHGQE667-Q1QU9gG3.js → wardleyDiagram-EHGQE667-Cj4CQn1M.js} +1 -1
- package/dist/client/assets/{xychartDiagram-FW5EYKEG-CzYpxk4l.js → xychartDiagram-FW5EYKEG-_uBCjai8.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/sw.js +1 -1
- package/dist/server/agent/autoTitle.js +118 -0
- package/dist/server/agent/installers.js +11 -10
- package/dist/server/agent/plugins.js +90 -4
- package/dist/server/agent/registry.js +1 -0
- package/dist/server/agent/session.js +18 -2
- package/dist/server/agent/titleNamerCatalog.js +203 -0
- package/dist/server/agentHook.js +2 -1
- package/dist/server/cli.js +218 -30
- package/dist/server/routes/filesystem.js +11 -8
- package/dist/server/routes/terminal.js +351 -10
- package/dist/server/utils/autoUpdate.js +175 -0
- package/dist/server/utils/npmUpdate.js +2 -1
- package/dist/server/utils/settings.js +44 -0
- package/package.json +1 -1
- package/runtime-manifest.json +2 -2
- package/dist/client/assets/channel-C2i7H7bH.js +0 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-BlGG6XFj.js +0 -1
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-BlGG6XFj.js +0 -1
- package/dist/client/assets/index-ChhyERKX.css +0 -1
- package/dist/client/assets/index-r-PkH7SB.js +0 -442
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-beoQXpWJ.js +0 -1
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-BxL1xNhC.js +0 -8
package/dist/server/cli.js
CHANGED
|
@@ -16,10 +16,11 @@ import https from 'https';
|
|
|
16
16
|
import path from 'path';
|
|
17
17
|
import readline from 'readline';
|
|
18
18
|
import { createHash, randomUUID } from 'crypto';
|
|
19
|
-
import { spawn, execFile } from 'child_process';
|
|
19
|
+
import { spawn, spawnSync, execFile } from 'child_process';
|
|
20
20
|
import { promisify } from 'util';
|
|
21
21
|
import { Writable } from 'stream';
|
|
22
22
|
import { createRequire } from 'module';
|
|
23
|
+
import { fileURLToPath } from 'url';
|
|
23
24
|
import { PORT, DEFAULT_HOST, TMUX } from './config.js';
|
|
24
25
|
import { isFirstRunCompleted, markFirstRunCompleted, normalizeLocalAccessName, setLocalAccessSetting, getLocalAccessSetting, getPreventSleepSetting } from './utils/settings.js';
|
|
25
26
|
import { runBootChecks, formatBootCheckReport } from './utils/bootCheck.js';
|
|
@@ -126,7 +127,8 @@ Options:
|
|
|
126
127
|
--tls List termdock-managed tmux sessions (reads tmux directly,
|
|
127
128
|
no server connection required)
|
|
128
129
|
-a, --all With --tls/--attach-tmux: include tmux sessions not stamped by termdock
|
|
129
|
-
--json With --tls: emit JSON
|
|
130
|
+
--json With --tls: emit session JSON; with --plugin-init:
|
|
131
|
+
emit the machine-readable Agent integration protocol
|
|
130
132
|
--attach-tmux [n] Attach to a termdock-managed tmux session.
|
|
131
133
|
With no name: interactive picker.
|
|
132
134
|
With name: attach directly (e.g. --attach-tmux wt-foo).
|
|
@@ -154,7 +156,7 @@ Options:
|
|
|
154
156
|
--change-audit-explain <id> [cwd]
|
|
155
157
|
Read a natural-language explanation from stdin and inject
|
|
156
158
|
it for the specified Termdock hunk ID.
|
|
157
|
-
--plugin-init Print the
|
|
159
|
+
--plugin-init Print the Agent integration protocol reference
|
|
158
160
|
--plugin-create <file>
|
|
159
161
|
Create a plugin from a manifest JSON file
|
|
160
162
|
--plugin-list List installed agent plugins
|
|
@@ -163,7 +165,12 @@ Options:
|
|
|
163
165
|
-h, --help Show this help message
|
|
164
166
|
|
|
165
167
|
Short commands:
|
|
166
|
-
|
|
168
|
+
agent-plugin [--json]
|
|
169
|
+
Explain how an Agent integrates itself. JSON mode is
|
|
170
|
+
machine-readable and intended for Agent consumption.
|
|
171
|
+
agent-event <slug> <event> [status]
|
|
172
|
+
Emit one lifecycle/status event from an Agent hook.
|
|
173
|
+
pi Same as agent-plugin
|
|
167
174
|
plugin-create <file>
|
|
168
175
|
Same as --plugin-create <file>
|
|
169
176
|
plugin-list Same as --plugin-list
|
|
@@ -504,9 +511,11 @@ function parseArgs(argv) {
|
|
|
504
511
|
let changeAuditShow;
|
|
505
512
|
let injectChangeAuditHunk;
|
|
506
513
|
let pluginInit = false;
|
|
514
|
+
let pluginInitJson = false;
|
|
507
515
|
let pluginCreate;
|
|
508
516
|
let pluginList = false;
|
|
509
517
|
let pluginRemove;
|
|
518
|
+
let agentEvent;
|
|
510
519
|
// Short command aliases for the common path. Keep these positional-only so
|
|
511
520
|
// long-form flags remain the single source of truth for option semantics.
|
|
512
521
|
if (argv[0] && !argv[0].startsWith('-')) {
|
|
@@ -606,9 +615,22 @@ function parseArgs(argv) {
|
|
|
606
615
|
injectChangeAuditHunk = { id: next, cwd };
|
|
607
616
|
argv = argv.slice(cwd ? 3 : 2);
|
|
608
617
|
}
|
|
609
|
-
else if (command === 'pi' || command === 'plugin-init') {
|
|
618
|
+
else if (command === 'pi' || command === 'plugin-init' || command === 'agent-plugin') {
|
|
610
619
|
pluginInit = true;
|
|
611
|
-
|
|
620
|
+
pluginInitJson = next === '--json';
|
|
621
|
+
argv = argv.slice(pluginInitJson ? 2 : 1);
|
|
622
|
+
}
|
|
623
|
+
else if (command === 'agent-event') {
|
|
624
|
+
const event = argv[2];
|
|
625
|
+
const customStatus = argv[3];
|
|
626
|
+
if (next && event && !next.startsWith('-') && !event.startsWith('-')) {
|
|
627
|
+
agentEvent = { slug: next, event, status: customStatus && !customStatus.startsWith('-') ? customStatus : undefined };
|
|
628
|
+
argv = argv.slice(agentEvent.status ? 4 : 3);
|
|
629
|
+
}
|
|
630
|
+
else {
|
|
631
|
+
console.error(`${ICON.err} ${c.red('Usage: td agent-event <slug> <event> [status]')}`);
|
|
632
|
+
process.exit(1);
|
|
633
|
+
}
|
|
612
634
|
}
|
|
613
635
|
else if (command === 'pcreate' || command === 'plugin-create') {
|
|
614
636
|
if (next && !next.startsWith('-')) {
|
|
@@ -713,7 +735,10 @@ function parseArgs(argv) {
|
|
|
713
735
|
continue;
|
|
714
736
|
}
|
|
715
737
|
if (arg === '--json') {
|
|
716
|
-
|
|
738
|
+
if (pluginInit)
|
|
739
|
+
pluginInitJson = true;
|
|
740
|
+
else
|
|
741
|
+
tlsJson = true;
|
|
717
742
|
continue;
|
|
718
743
|
}
|
|
719
744
|
if (arg === '--attach-tmux') {
|
|
@@ -887,9 +912,11 @@ function parseArgs(argv) {
|
|
|
887
912
|
changeAuditShow,
|
|
888
913
|
injectChangeAuditHunk,
|
|
889
914
|
pluginInit,
|
|
915
|
+
pluginInitJson,
|
|
890
916
|
pluginCreate,
|
|
891
917
|
pluginList,
|
|
892
918
|
pluginRemove,
|
|
919
|
+
agentEvent,
|
|
893
920
|
};
|
|
894
921
|
}
|
|
895
922
|
// Reads a single line from stdin without echoing keystrokes. Used for password
|
|
@@ -1664,10 +1691,124 @@ function requireRunningServer() {
|
|
|
1664
1691
|
}
|
|
1665
1692
|
return { baseUrl: resolveServerBaseUrl(state), token };
|
|
1666
1693
|
}
|
|
1694
|
+
const AGENT_EVENT_KINDS = [
|
|
1695
|
+
'session-start',
|
|
1696
|
+
'prompt-submit',
|
|
1697
|
+
'permission-request',
|
|
1698
|
+
'question-asked',
|
|
1699
|
+
'tool-complete',
|
|
1700
|
+
'notification',
|
|
1701
|
+
'stop',
|
|
1702
|
+
'session-end',
|
|
1703
|
+
];
|
|
1704
|
+
const AGENT_PLUGIN_EXAMPLE = {
|
|
1705
|
+
version: 2,
|
|
1706
|
+
slug: 'my-agent',
|
|
1707
|
+
displayName: 'My Agent',
|
|
1708
|
+
aliases: ['my-agent'],
|
|
1709
|
+
accentColor: '#4385BE',
|
|
1710
|
+
iconMode: 'mask',
|
|
1711
|
+
statuses: [
|
|
1712
|
+
{ id: 'thinking', phase: 'working', label: 'Thinking', indicator: 'spinner', tone: 'info' },
|
|
1713
|
+
{ id: 'approval', phase: 'waiting', label: 'Needs approval', indicator: 'question', tone: 'warning' },
|
|
1714
|
+
{ id: 'complete', phase: 'done', label: 'Complete', indicator: 'badge', tone: 'success' },
|
|
1715
|
+
],
|
|
1716
|
+
hooks: {
|
|
1717
|
+
target: '~/.my-agent/settings.json',
|
|
1718
|
+
events: [
|
|
1719
|
+
{ hook: 'UserPromptSubmit', event: 'prompt-submit', status: 'thinking' },
|
|
1720
|
+
{ hook: 'PermissionRequest', event: 'permission-request', status: 'approval' },
|
|
1721
|
+
{ hook: 'Stop', event: 'stop', status: 'complete' },
|
|
1722
|
+
],
|
|
1723
|
+
},
|
|
1724
|
+
resume: {
|
|
1725
|
+
command: 'my-agent --resume {sessionId}',
|
|
1726
|
+
staleFlags: ['--resume'],
|
|
1727
|
+
},
|
|
1728
|
+
};
|
|
1729
|
+
function runPluginInitJson() {
|
|
1730
|
+
console.log(JSON.stringify({
|
|
1731
|
+
protocol: 'termdock-agent-plugin',
|
|
1732
|
+
version: 2,
|
|
1733
|
+
purpose: 'Register this Agent with Termdock and publish lifecycle/status updates.',
|
|
1734
|
+
commands: {
|
|
1735
|
+
guide: 'td agent-plugin --json',
|
|
1736
|
+
register: 'td plugin-create ./manifest.json',
|
|
1737
|
+
emit: 'td agent-event <slug> <event> [status]',
|
|
1738
|
+
inspect: 'td plugin-list',
|
|
1739
|
+
remove: 'td plugin-remove <slug>',
|
|
1740
|
+
},
|
|
1741
|
+
ui: {
|
|
1742
|
+
location: 'Settings > Detection Rules',
|
|
1743
|
+
registration: 'A successfully created plugin appears immediately without restarting Termdock.',
|
|
1744
|
+
install: 'The user clicks Install to write the declared hook mappings into hooks.target.',
|
|
1745
|
+
uninstall: 'The user clicks Uninstall to remove only Termdock-owned hook entries.',
|
|
1746
|
+
},
|
|
1747
|
+
lifecycleEvents: AGENT_EVENT_KINDS,
|
|
1748
|
+
phases: ['idle', 'working', 'waiting', 'done'],
|
|
1749
|
+
indicators: ['spinner', 'pulse', 'dot', 'ring', 'badge', 'terminal', 'question'],
|
|
1750
|
+
tones: ['neutral', 'info', 'success', 'warning', 'danger', 'accent'],
|
|
1751
|
+
statusModel: {
|
|
1752
|
+
phase: 'Stable Termdock behavior used for notifications and attention.',
|
|
1753
|
+
id: 'Agent-defined fine-grained status referenced by hook mappings or agent-event.',
|
|
1754
|
+
label: 'Human-readable status text.',
|
|
1755
|
+
indicator: 'Theme-safe visual indicator.',
|
|
1756
|
+
tone: 'Theme token; arbitrary status colors are not accepted.',
|
|
1757
|
+
},
|
|
1758
|
+
migrationV1: {
|
|
1759
|
+
errorCode: 'AGENT_PLUGIN_MANIFEST_V1_UNSUPPORTED',
|
|
1760
|
+
guideCommand: 'td agent-plugin --json',
|
|
1761
|
+
aiPrompt: 'Please migrate the attached Termdock Agent plugin manifest from v1 to v2. Preserve slug, displayName, aliases, accentColor, iconMode, hooks.target, and resume. Add statuses[] entries with id, phase (idle|working|waiting|done), label, indicator, and tone. Add a status reference to each relevant hooks.events[] mapping while keeping its core event value. Use the output of `td agent-plugin --json` as the authoritative schema. Return only the corrected manifest JSON.',
|
|
1762
|
+
},
|
|
1763
|
+
hookPayload: {
|
|
1764
|
+
stdin: 'Optional JSON object.',
|
|
1765
|
+
recognizedFields: ['session_id', 'sessionId', 'message', 'cwd'],
|
|
1766
|
+
example: 'printf \'%s\' \'{"session_id":"abc","message":"Indexing"}\' | td agent-event my-agent prompt-submit thinking',
|
|
1767
|
+
},
|
|
1768
|
+
integrationSteps: [
|
|
1769
|
+
'Generate a version 2 manifest using this schema and save it as JSON.',
|
|
1770
|
+
'Run td plugin-create <manifest-file>; the Agent appears in Termdock immediately.',
|
|
1771
|
+
'Ask the user to open Settings > Detection Rules and click Install for this Agent. They can uninstall it there later.',
|
|
1772
|
+
'If the Agent hook format is not a Claude-style hooks map, configure native hooks to call td agent-event directly.',
|
|
1773
|
+
'Run td plugin-list to verify registration.',
|
|
1774
|
+
],
|
|
1775
|
+
manifest: AGENT_PLUGIN_EXAMPLE,
|
|
1776
|
+
}, null, 2));
|
|
1777
|
+
}
|
|
1778
|
+
function runAgentEvent(input) {
|
|
1779
|
+
const slugPattern = /^[a-z][a-z0-9-]{0,39}$/;
|
|
1780
|
+
if (!slugPattern.test(input.slug)) {
|
|
1781
|
+
console.error(`${ICON.err} ${c.red('agent-event slug must be lowercase alphanumeric with hyphens')}`);
|
|
1782
|
+
process.exit(1);
|
|
1783
|
+
}
|
|
1784
|
+
if (!AGENT_EVENT_KINDS.includes(input.event)) {
|
|
1785
|
+
console.error(`${ICON.err} ${c.red(`Unknown agent event: ${input.event}`)}`);
|
|
1786
|
+
console.error(` ${c.dim(`Expected one of: ${AGENT_EVENT_KINDS.join(', ')}`)}`);
|
|
1787
|
+
process.exit(1);
|
|
1788
|
+
}
|
|
1789
|
+
if (input.status && !slugPattern.test(input.status)) {
|
|
1790
|
+
console.error(`${ICON.err} ${c.red('agent-event status must be lowercase alphanumeric with hyphens')}`);
|
|
1791
|
+
process.exit(1);
|
|
1792
|
+
}
|
|
1793
|
+
const script = fileURLToPath(new URL('./agentHook.js', import.meta.url));
|
|
1794
|
+
const result = spawnSync(process.execPath, [script, 'agent-hook', input.slug, input.event, ...(input.status ? [input.status] : [])], {
|
|
1795
|
+
stdio: 'inherit',
|
|
1796
|
+
env: process.env,
|
|
1797
|
+
});
|
|
1798
|
+
if (result.error) {
|
|
1799
|
+
console.error(`${ICON.err} ${c.red(`Failed to emit Agent event: ${result.error.message}`)}`);
|
|
1800
|
+
process.exit(1);
|
|
1801
|
+
}
|
|
1802
|
+
process.exit(result.status ?? 0);
|
|
1803
|
+
}
|
|
1667
1804
|
function runPluginInit() {
|
|
1668
1805
|
console.log(`\
|
|
1669
|
-
${c.bold('Agent
|
|
1670
|
-
${c.dim('
|
|
1806
|
+
${c.bold('Termdock Agent Integration Protocol v2')}
|
|
1807
|
+
${c.dim('──────────────────────────────────────')}
|
|
1808
|
+
|
|
1809
|
+
Machine-readable form for an Agent to inspect itself:
|
|
1810
|
+
|
|
1811
|
+
${c.cyan('td agent-plugin --json')}
|
|
1671
1812
|
|
|
1672
1813
|
A plugin is a directory under ${c.cyan('~/.termdock/agent-plugins/<slug>/')} containing:
|
|
1673
1814
|
|
|
@@ -1678,8 +1819,8 @@ ${c.bold('manifest.json schema')}
|
|
|
1678
1819
|
${c.dim('───────────────────')}
|
|
1679
1820
|
|
|
1680
1821
|
{
|
|
1681
|
-
${c.dim('// Manifest version. Must be')} ${c.green('
|
|
1682
|
-
"version":
|
|
1822
|
+
${c.dim('// Manifest version. Must be')} ${c.green('2')}${c.dim('.')}
|
|
1823
|
+
"version": 2,
|
|
1683
1824
|
|
|
1684
1825
|
${c.dim('// Unique slug (lowercase alphanum + hyphens, max 40 chars).')}
|
|
1685
1826
|
${c.dim('// Must not collide with built-in agent slugs.')}
|
|
@@ -1702,6 +1843,13 @@ ${c.dim('───────────────────')}
|
|
|
1702
1843
|
${c.dim('// "native" - SVG rendered directly, preserving original fills/gradients.')}
|
|
1703
1844
|
"iconMode": "native",
|
|
1704
1845
|
|
|
1846
|
+
${c.dim('// Optional fine-grained states. phase drives behavior; the rest drives UI.')}
|
|
1847
|
+
"statuses": [
|
|
1848
|
+
{ "id": "thinking", "phase": "working", "label": "Thinking", "indicator": "spinner", "tone": "info" },
|
|
1849
|
+
{ "id": "approval", "phase": "waiting", "label": "Needs approval", "indicator": "question", "tone": "warning" },
|
|
1850
|
+
{ "id": "complete", "phase": "done", "label": "Complete", "indicator": "badge", "tone": "success" }
|
|
1851
|
+
],
|
|
1852
|
+
|
|
1705
1853
|
${c.dim('// ── optional: hook installation (bring your own hooks.json surface) ──')}
|
|
1706
1854
|
"hooks": {
|
|
1707
1855
|
${c.dim("// Path to the agent's hooks config file (~-abbreviated).")}
|
|
@@ -1712,10 +1860,10 @@ ${c.dim('───────────────────')}
|
|
|
1712
1860
|
${c.dim('// Optional matcher narrows the subscription (e.g. permission type).')}
|
|
1713
1861
|
"events": [
|
|
1714
1862
|
{ "hook": "SessionStart", "event": "session-start" },
|
|
1715
|
-
{ "hook": "UserPromptSubmit", "event": "prompt-submit" },
|
|
1716
|
-
{ "hook": "Notification", "event": "notification", "matcher": "elicitation_dialog" },
|
|
1863
|
+
{ "hook": "UserPromptSubmit", "event": "prompt-submit", "status": "thinking" },
|
|
1864
|
+
{ "hook": "Notification", "event": "notification", "status": "approval", "matcher": "elicitation_dialog" },
|
|
1717
1865
|
{ "hook": "PostToolUse", "event": "tool-complete" },
|
|
1718
|
-
{ "hook": "Stop", "event": "stop" },
|
|
1866
|
+
{ "hook": "Stop", "event": "stop", "status": "complete" },
|
|
1719
1867
|
{ "hook": "SessionEnd", "event": "session-end" }
|
|
1720
1868
|
]
|
|
1721
1869
|
},
|
|
@@ -1744,28 +1892,48 @@ ${c.dim('─────────────────────')}
|
|
|
1744
1892
|
${c.bold('Creating a plugin')}
|
|
1745
1893
|
${c.dim('──────────────────')}
|
|
1746
1894
|
|
|
1747
|
-
#
|
|
1748
|
-
mkdir -p ~/.termdock/agent-plugins/trae
|
|
1749
|
-
cp manifest.json icon.svg ~/.termdock/agent-plugins/trae/
|
|
1750
|
-
|
|
1751
|
-
${c.dim('# 2. Install the plugin via CLI (needs running server)')}
|
|
1752
|
-
td plugin-create ~/.termdock/agent-plugins/trae/manifest.json
|
|
1753
|
-
|
|
1754
|
-
${c.dim('# 3. Or create directly from the manifest JSON')}
|
|
1895
|
+
${c.dim('# Inject the Agent definition; it appears in the UI immediately')}
|
|
1755
1896
|
td plugin-create ./trae-manifest.json
|
|
1756
1897
|
|
|
1757
|
-
${c.dim('#
|
|
1898
|
+
${c.dim('# The user then opens Settings > Detection Rules and clicks Install.')}
|
|
1899
|
+
${c.dim('# The same UI button can uninstall the hooks later.')}
|
|
1900
|
+
|
|
1901
|
+
${c.dim('# Verify registration')}
|
|
1758
1902
|
td plugin-list
|
|
1759
1903
|
|
|
1760
|
-
${c.dim('#
|
|
1904
|
+
${c.dim('# Remove')}
|
|
1761
1905
|
td plugin-remove trae
|
|
1762
1906
|
|
|
1907
|
+
${c.bold('Direct event bridge for any hook system')}
|
|
1908
|
+
${c.dim('───────────────────────────────────────')}
|
|
1909
|
+
|
|
1910
|
+
If the Agent does not use a Claude-style hooks JSON file, configure its
|
|
1911
|
+
native hooks to invoke this stable public CLI command instead:
|
|
1912
|
+
|
|
1913
|
+
td agent-event trae prompt-submit thinking
|
|
1914
|
+
td agent-event trae permission-request approval
|
|
1915
|
+
td agent-event trae stop complete
|
|
1916
|
+
|
|
1917
|
+
Hook JSON may be piped on stdin. Termdock recognizes session_id/sessionId,
|
|
1918
|
+
message, and cwd:
|
|
1919
|
+
|
|
1920
|
+
printf '%s' '{"session_id":"abc","message":"Indexing"}' | td agent-event trae prompt-submit thinking
|
|
1921
|
+
|
|
1763
1922
|
${c.bold('Manifest validation at startup')}
|
|
1764
1923
|
${c.dim('───────────────────────────────')}
|
|
1765
1924
|
|
|
1766
|
-
Termdock validates every plugin manifest
|
|
1925
|
+
Termdock validates every plugin manifest before registration. Errors are logged
|
|
1767
1926
|
to ${c.cyan('~/.termdock/server.log')} and visible via ${c.cyan('td plugin-list')}${c.dim('.')}
|
|
1768
1927
|
Slugs/aliases that collide with built-in agents are skipped.
|
|
1928
|
+
|
|
1929
|
+
${c.bold('Migrating a v1 plugin')}
|
|
1930
|
+
${c.dim('─────────────────────')}
|
|
1931
|
+
|
|
1932
|
+
v1 manifests fail with ${c.cyan('AGENT_PLUGIN_MANIFEST_V1_UNSUPPORTED')} and an
|
|
1933
|
+
AI-ready repair prompt. Copy that prompt together with the old manifest to
|
|
1934
|
+
an AI, or let the AI inspect the current schema directly:
|
|
1935
|
+
|
|
1936
|
+
${c.cyan('td agent-plugin --json')}
|
|
1769
1937
|
`);
|
|
1770
1938
|
}
|
|
1771
1939
|
async function runPluginCreate(manifestPath) {
|
|
@@ -1790,12 +1958,20 @@ async function runPluginCreate(manifestPath) {
|
|
|
1790
1958
|
const response = await postLocalJson(baseUrl, token, '/api/terminal/agent-plugins', manifest);
|
|
1791
1959
|
const body = JSON.parse(response.body || '{}');
|
|
1792
1960
|
if (response.statusCode < 200 || response.statusCode >= 300) {
|
|
1793
|
-
console.error(`${ICON.err} ${c.red(body.
|
|
1961
|
+
console.error(`${ICON.err} ${c.red(body.code ? `${body.code}:` : 'Plugin validation failed:')}`);
|
|
1962
|
+
console.error(body.error || 'Failed to create plugin');
|
|
1963
|
+
if (body.migration?.guideCommand) {
|
|
1964
|
+
console.error(`\n${c.bold('Current schema:')} ${c.cyan(body.migration.guideCommand)}`);
|
|
1965
|
+
}
|
|
1966
|
+
if (body.migration?.aiPrompt) {
|
|
1967
|
+
console.error(`\n${c.bold('Copy this to your AI:')}\n${body.migration.aiPrompt}`);
|
|
1968
|
+
}
|
|
1794
1969
|
process.exit(1);
|
|
1795
1970
|
}
|
|
1796
1971
|
console.log(`${ICON.ok} ${c.green(`Plugin "${body.slug}" created.`)}`);
|
|
1797
1972
|
console.log(` ${c.dim('Directory:')} ${c.cyan(body.dir ?? resolved)}`);
|
|
1798
|
-
console.log(` ${c.dim('
|
|
1973
|
+
console.log(` ${c.dim('Visible immediately in Settings > Detection Rules; no restart required.')}`);
|
|
1974
|
+
console.log(` ${c.dim('The user can click Install or Uninstall there.')}`);
|
|
1799
1975
|
}
|
|
1800
1976
|
async function runPluginList() {
|
|
1801
1977
|
const { baseUrl, token } = requireRunningServer();
|
|
@@ -1808,7 +1984,7 @@ async function runPluginList() {
|
|
|
1808
1984
|
const plugins = body.plugins ?? [];
|
|
1809
1985
|
if (plugins.length === 0) {
|
|
1810
1986
|
console.log(`${c.dim('No plugins installed.')}`);
|
|
1811
|
-
console.log(` ${c.dim('See:')} ${c.cyan('td plugin
|
|
1987
|
+
console.log(` ${c.dim('See:')} ${c.cyan('td agent-plugin')}`);
|
|
1812
1988
|
}
|
|
1813
1989
|
else {
|
|
1814
1990
|
console.log(`${c.bold('Installed plugins')} ${c.dim(`(${plugins.length})`)}`);
|
|
@@ -1831,7 +2007,13 @@ async function runPluginList() {
|
|
|
1831
2007
|
console.log('');
|
|
1832
2008
|
console.log(`${c.yellow('Validation errors')}`);
|
|
1833
2009
|
for (const e of errors) {
|
|
1834
|
-
console.log(` ${c.red('✗')} ${c.cyan(e.slug)}${c.dim(':')} ${e.errors.join('; ')}`);
|
|
2010
|
+
console.log(` ${c.red('✗')} ${c.cyan(e.slug)}${c.dim(':')} ${e.code ? c.yellow(e.code) : e.errors.join('; ')}`);
|
|
2011
|
+
for (const message of e.errors)
|
|
2012
|
+
console.log(` ${message}`);
|
|
2013
|
+
if (e.migration?.guideCommand)
|
|
2014
|
+
console.log(` ${c.dim('Current schema:')} ${c.cyan(e.migration.guideCommand)}`);
|
|
2015
|
+
if (e.migration?.aiPrompt)
|
|
2016
|
+
console.log(` ${c.dim('Copy this to your AI:')} ${e.migration.aiPrompt}`);
|
|
1835
2017
|
}
|
|
1836
2018
|
}
|
|
1837
2019
|
}
|
|
@@ -2809,8 +2991,14 @@ async function main() {
|
|
|
2809
2991
|
runClearPassword();
|
|
2810
2992
|
process.exit(0);
|
|
2811
2993
|
}
|
|
2994
|
+
if (options.agentEvent) {
|
|
2995
|
+
runAgentEvent(options.agentEvent);
|
|
2996
|
+
}
|
|
2812
2997
|
if (options.pluginInit) {
|
|
2813
|
-
|
|
2998
|
+
if (options.pluginInitJson)
|
|
2999
|
+
runPluginInitJson();
|
|
3000
|
+
else
|
|
3001
|
+
runPluginInit();
|
|
2814
3002
|
process.exit(0);
|
|
2815
3003
|
}
|
|
2816
3004
|
if (options.pluginCreate !== undefined) {
|
|
@@ -905,7 +905,7 @@ function buildNestedRepoDisplayRootSet(workspaceRoot, repositories) {
|
|
|
905
905
|
async function buildGitBundle(resolvedCwd, gitRoot, signal, options = {}) {
|
|
906
906
|
const [branchOutput, changedResult] = await Promise.all([
|
|
907
907
|
execGit(['branch', '--show-current'], gitRoot, signal, options.gitTimeoutMs).catch(emptyOnNonAbortGitError),
|
|
908
|
-
getChangedFiles(gitRoot, signal, { includeUntracked:
|
|
908
|
+
getChangedFiles(gitRoot, signal, { includeUntracked: true, gitTimeoutMs: options.gitTimeoutMs }),
|
|
909
909
|
]);
|
|
910
910
|
const files = changedResult.files;
|
|
911
911
|
const annotatedFiles = annotateRepoFiles(files, gitRoot, gitRoot);
|
|
@@ -1012,7 +1012,6 @@ function mergeChangedFilesByPath(current, incoming) {
|
|
|
1012
1012
|
function updateGitBundleCachesWithUntracked(repoRoot, files) {
|
|
1013
1013
|
if (files.length === 0)
|
|
1014
1014
|
return;
|
|
1015
|
-
const now = Date.now();
|
|
1016
1015
|
for (const [cacheKey, cached] of gitBundleCache.entries()) {
|
|
1017
1016
|
const bundle = cached.bundle;
|
|
1018
1017
|
const repositories = bundle.repositories;
|
|
@@ -1046,7 +1045,7 @@ function updateGitBundleCachesWithUntracked(repoRoot, files) {
|
|
|
1046
1045
|
repoFilters: buildGitRepositoryFilters(primary?.root ?? repoRoot, nextRepositories),
|
|
1047
1046
|
untrackedDeferred: nextRepositories.some((repo) => repo.untrackedDeferred),
|
|
1048
1047
|
};
|
|
1049
|
-
gitBundleCache.set(cacheKey, { bundle: nextBundle, expiresAt:
|
|
1048
|
+
gitBundleCache.set(cacheKey, { bundle: nextBundle, expiresAt: cached.expiresAt, updatedAt: cached.updatedAt });
|
|
1050
1049
|
continue;
|
|
1051
1050
|
}
|
|
1052
1051
|
if (bundle.context?.root !== repoRoot)
|
|
@@ -1063,7 +1062,7 @@ function updateGitBundleCachesWithUntracked(repoRoot, files) {
|
|
|
1063
1062
|
context: nextContext,
|
|
1064
1063
|
untrackedDeferred: false,
|
|
1065
1064
|
};
|
|
1066
|
-
gitBundleCache.set(cacheKey, { bundle: nextBundle, expiresAt:
|
|
1065
|
+
gitBundleCache.set(cacheKey, { bundle: nextBundle, expiresAt: cached.expiresAt, updatedAt: cached.updatedAt });
|
|
1067
1066
|
}
|
|
1068
1067
|
}
|
|
1069
1068
|
async function buildWorkspaceGitBundle(resolvedCwd, gitRoot, includeNested, signal, options = {}) {
|
|
@@ -1138,6 +1137,7 @@ async function getCachedGitBundle(resolvedCwd, gitRoot, includeNested, refresh,
|
|
|
1138
1137
|
cached: true,
|
|
1139
1138
|
stale: cached.expiresAt <= now,
|
|
1140
1139
|
cacheAgeMs,
|
|
1140
|
+
cacheUpdatedAt: cached.updatedAt,
|
|
1141
1141
|
};
|
|
1142
1142
|
}
|
|
1143
1143
|
const canReusePending = !refresh && !signal;
|
|
@@ -1148,8 +1148,9 @@ async function getCachedGitBundle(resolvedCwd, gitRoot, includeNested, refresh,
|
|
|
1148
1148
|
}
|
|
1149
1149
|
const promise = buildWorkspaceGitBundle(resolvedCwd, gitRoot, includeNested, signal)
|
|
1150
1150
|
.then((bundle) => {
|
|
1151
|
-
|
|
1152
|
-
|
|
1151
|
+
const updatedAt = Date.now();
|
|
1152
|
+
gitBundleCache.set(cacheKey, { bundle, expiresAt: updatedAt + GIT_BUNDLE_CACHE_TTL_MS, updatedAt });
|
|
1153
|
+
return { ...bundle, cacheUpdatedAt: updatedAt };
|
|
1153
1154
|
})
|
|
1154
1155
|
.finally(() => {
|
|
1155
1156
|
if (gitBundleBuildPromises.get(cacheKey) === promise)
|
|
@@ -1166,8 +1167,9 @@ async function refreshGitBundleCacheDetached(resolvedCwd, gitRoot, includeNested
|
|
|
1166
1167
|
return pending;
|
|
1167
1168
|
const promise = buildWorkspaceGitBundle(resolvedCwd, gitRoot, includeNested, undefined, options)
|
|
1168
1169
|
.then((bundle) => {
|
|
1169
|
-
|
|
1170
|
-
|
|
1170
|
+
const updatedAt = Date.now();
|
|
1171
|
+
gitBundleCache.set(cacheKey, { bundle, expiresAt: updatedAt + GIT_BUNDLE_CACHE_TTL_MS, updatedAt });
|
|
1172
|
+
return { ...bundle, cacheUpdatedAt: updatedAt };
|
|
1171
1173
|
})
|
|
1172
1174
|
.finally(() => {
|
|
1173
1175
|
if (gitBundleBuildPromises.get(cacheKey) === promise)
|
|
@@ -1190,6 +1192,7 @@ function getGitBundleCache(gitRoot, includeNested, allowStale = false) {
|
|
|
1190
1192
|
cached: true,
|
|
1191
1193
|
stale: cached.expiresAt <= now,
|
|
1192
1194
|
cacheAgeMs,
|
|
1195
|
+
cacheUpdatedAt: cached.updatedAt,
|
|
1193
1196
|
};
|
|
1194
1197
|
}
|
|
1195
1198
|
function getSinglePath(paths) {
|