termdock 1.4.69 → 1.4.71
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-CG6vLwyw.js} +1 -1
- package/dist/client/assets/{abnfDiagram-VRR7QNED-BYiQXGYk.js → abnfDiagram-VRR7QNED-CrXEZLWX.js} +1 -1
- package/dist/client/assets/{arc-jlT_jmoZ.js → arc-DbKGR7Ex.js} +1 -1
- package/dist/client/assets/{architectureDiagram-ZJ3FMSHR-D1qZ875Q.js → architectureDiagram-ZJ3FMSHR-PF5BoUbI.js} +1 -1
- package/dist/client/assets/{blockDiagram-677ZJIJ3-qkgn2Ysc.js → blockDiagram-677ZJIJ3-D1gRzVWB.js} +1 -1
- package/dist/client/assets/{c4Diagram-LMCZKHZV-JgcH49Pf.js → c4Diagram-LMCZKHZV-CwvOLync.js} +1 -1
- package/dist/client/assets/channel-JTEuKVy6.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-E-3iflEy.js → chunk-2Q5K7J3B-BeRdP-xP.js} +1 -1
- package/dist/client/assets/{chunk-32BRIVSS-mM01y3kF.js → chunk-32BRIVSS-CrqR5rvl.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-GO8Muo5l.js → chunk-5VM5RSS4-CjbUr-ko.js} +1 -1
- package/dist/client/assets/{chunk-EX3LRPZG-BUSDluH0.js → chunk-EX3LRPZG-BVEk3Wma.js} +1 -1
- package/dist/client/assets/{chunk-JWPE2WC7-obSL6EzJ.js → chunk-JWPE2WC7-WitJe8kR.js} +1 -1
- package/dist/client/assets/{chunk-MOJQB5TN-zEktbtAG.js → chunk-MOJQB5TN-BCewlLYk.js} +1 -1
- package/dist/client/assets/{chunk-RYQCIY6F-kH7H8XgQ.js → chunk-RYQCIY6F-CoP0AA4G.js} +1 -1
- package/dist/client/assets/{chunk-V7JOEXUC-BqDFpHKN.js → chunk-V7JOEXUC-CSZrnmp_.js} +1 -1
- package/dist/client/assets/{chunk-VR4S4FIN-BGZ7kCIG.js → chunk-VR4S4FIN-BB-7zz-9.js} +1 -1
- package/dist/client/assets/{chunk-XXDRQBXY-D-TatWXe.js → chunk-XXDRQBXY-CcZZlCik.js} +1 -1
- package/dist/client/assets/classDiagram-OUVF2IWQ-BiFJB_5s.js +1 -0
- package/dist/client/assets/classDiagram-v2-EOCWNBFH-BiFJB_5s.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-CfWyrViz.js → cose-bilkent-JH36ORCC-DcmzUDwh.js} +1 -1
- package/dist/client/assets/{cynefin-VYW2F7L2-BFdZ3FcE.js → cynefin-VYW2F7L2-C1XlDVoJ.js} +1 -1
- package/dist/client/assets/{cynefinDiagram-TSTJHNR4-iAfCVoFU.js → cynefinDiagram-TSTJHNR4-zY6Aet2S.js} +1 -1
- package/dist/client/assets/{dagre-VKFMJZFB-CZhh0hrr.js → dagre-VKFMJZFB-DqGTi6zR.js} +1 -1
- package/dist/client/assets/{diagram-FQU43EPY-BqUK2OmV.js → diagram-FQU43EPY-DStcsU7S.js} +1 -1
- package/dist/client/assets/{diagram-G47NLZAW-vC0pSQYC.js → diagram-G47NLZAW-Cs6IMNip.js} +1 -1
- package/dist/client/assets/{diagram-NH7WQ7WH-Dqc5oxed.js → diagram-NH7WQ7WH-yA2bpWN7.js} +1 -1
- package/dist/client/assets/{diagram-OA4YK3LP-DIIXckFE.js → diagram-OA4YK3LP-Dzf9M0PI.js} +1 -1
- package/dist/client/assets/{diagram-WEI45ONY-DR9_cFeh.js → diagram-WEI45ONY-D75lpQKA.js} +1 -1
- package/dist/client/assets/{ebnfDiagram-CCIWWBDH-21OO0OXC.js → ebnfDiagram-CCIWWBDH-CkQvIpWy.js} +1 -1
- package/dist/client/assets/{erDiagram-Q63AITRT-YKnVG3az.js → erDiagram-Q63AITRT-Ca00Vjxz.js} +1 -1
- package/dist/client/assets/{flowDiagram-23GEKE2U-Cm7TGErF.js → flowDiagram-23GEKE2U-CGrnSTEp.js} +1 -1
- package/dist/client/assets/{ganttDiagram-NO4QXBWP-D_76Effz.js → ganttDiagram-NO4QXBWP-0SHfOIP4.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-IHSO6WYX-CC1ysmVD.js → gitGraphDiagram-IHSO6WYX-BeigYWvs.js} +1 -1
- package/dist/client/assets/index-D5PKdbhW.js +451 -0
- package/dist/client/assets/index-DhfIiBLW.css +1 -0
- package/dist/client/assets/{infoDiagram-FWYZ7A6U-BbgpmhBz.js → infoDiagram-FWYZ7A6U-CiY1-xjN.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-FXEZZL3T-C94bY_je.js → ishikawaDiagram-FXEZZL3T-CmtDjUm_.js} +1 -1
- package/dist/client/assets/{journeyDiagram-5HDEW3XC-BQ0iFRxs.js → journeyDiagram-5HDEW3XC-B7qOr9LY.js} +1 -1
- package/dist/client/assets/{kanban-definition-HUTT4EX6-BtAexXPo.js → kanban-definition-HUTT4EX6-DO2P2Um3.js} +1 -1
- package/dist/client/assets/{linear-C2HnIdOK.js → linear-B832hJt0.js} +1 -1
- package/dist/client/assets/{mermaid.core-BbG4sJBF.js → mermaid.core-CsAzdiCU.js} +4 -4
- package/dist/client/assets/{mindmap-definition-LN4V7U3C-B-uvViws.js → mindmap-definition-LN4V7U3C-HI__vXhQ.js} +1 -1
- package/dist/client/assets/{pegDiagram-2B236MQR-DHYcR6tE.js → pegDiagram-2B236MQR--rZIwjXq.js} +1 -1
- package/dist/client/assets/{pieDiagram-ENE6RG2P-DEE9cD6M.js → pieDiagram-ENE6RG2P-D9jPf5x9.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-ABIIQ3AL-DMQP5jG3.js → quadrantDiagram-ABIIQ3AL-DTCP5ji2.js} +1 -1
- package/dist/client/assets/{railroadDiagram-RFXS5EU6-B-4WDTwc.js → railroadDiagram-RFXS5EU6-D2Gspvrl.js} +1 -1
- package/dist/client/assets/{requirementDiagram-TGXJPOKE-C2xw_q-R.js → requirementDiagram-TGXJPOKE-DEJC02IW.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-HTMAVEWB-wq0BPqhc.js → sankeyDiagram-HTMAVEWB-opOhKPap.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-DBY2YBRQ-BqpM0A9d.js → sequenceDiagram-DBY2YBRQ-_sz2CRAG.js} +1 -1
- package/dist/client/assets/{sizeCapture-X5ZJPWSS-Ba2xxyp_.js → sizeCapture-X5ZJPWSS-D0Q2QkYF.js} +1 -1
- package/dist/client/assets/{stateDiagram-2N3HPSRC-agPx5qAN.js → stateDiagram-2N3HPSRC-VgeSQTtW.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-6OUMAXLB-BVh2FRO-.js +1 -0
- package/dist/client/assets/{swimlanes-5IMT3BWC-Cy6Lzj_L.js → swimlanes-5IMT3BWC-ClvBvSl0.js} +2 -2
- package/dist/client/assets/swimlanesDiagram-G3AALYLV-C17V-kgm.js +8 -0
- package/dist/client/assets/{timeline-definition-FHXFAJF6-LBrVfbdp.js → timeline-definition-FHXFAJF6-BXNsrECV.js} +1 -1
- package/dist/client/assets/{vennDiagram-L72KCM5P-BiYIV7mX.js → vennDiagram-L72KCM5P-BgwiqZd-.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-EHGQE667-Q1QU9gG3.js → wardleyDiagram-EHGQE667-DiqN5ERx.js} +1 -1
- package/dist/client/assets/{xychartDiagram-FW5EYKEG-CzYpxk4l.js → xychartDiagram-FW5EYKEG-DQjFVqC6.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/sw.js +1 -1
- package/dist/server/agent/autoTitle.js +95 -0
- package/dist/server/agent/installers.js +106 -22
- package/dist/server/agent/plugins.js +135 -4
- package/dist/server/agent/registry.js +2 -1
- package/dist/server/agent/session.js +27 -2
- package/dist/server/agent/titleNamerCatalog.js +301 -0
- package/dist/server/agentHook.js +49 -1
- package/dist/server/cli.js +238 -30
- package/dist/server/routes/filesystem.js +11 -8
- package/dist/server/routes/terminal.js +382 -14
- package/dist/server/utils/autoUpdate.js +175 -0
- package/dist/server/utils/npmUpdate.js +2 -1
- package/dist/server/utils/settings.js +43 -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,134 @@ 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
|
+
titleNamer: {
|
|
1729
|
+
command: 'my-agent',
|
|
1730
|
+
args: ['title', '--json', '--prompt', '{prompt}', '--model={model}'],
|
|
1731
|
+
models: {
|
|
1732
|
+
command: 'my-agent',
|
|
1733
|
+
args: ['models', '--json'],
|
|
1734
|
+
},
|
|
1735
|
+
},
|
|
1736
|
+
};
|
|
1737
|
+
function runPluginInitJson() {
|
|
1738
|
+
console.log(JSON.stringify({
|
|
1739
|
+
protocol: 'termdock-agent-plugin',
|
|
1740
|
+
version: 2,
|
|
1741
|
+
purpose: 'Register this Agent with Termdock and publish lifecycle/status updates.',
|
|
1742
|
+
commands: {
|
|
1743
|
+
guide: 'td agent-plugin --json',
|
|
1744
|
+
register: 'td plugin-create ./manifest.json',
|
|
1745
|
+
emit: 'td agent-event <slug> <event> [status]',
|
|
1746
|
+
inspect: 'td plugin-list',
|
|
1747
|
+
remove: 'td plugin-remove <slug>',
|
|
1748
|
+
},
|
|
1749
|
+
ui: {
|
|
1750
|
+
location: 'Settings > Detection Rules',
|
|
1751
|
+
registration: 'A successfully created plugin appears immediately without restarting Termdock.',
|
|
1752
|
+
install: 'The user clicks Install to write the declared hook mappings into hooks.target.',
|
|
1753
|
+
uninstall: 'The user clicks Uninstall to remove only Termdock-owned hook entries.',
|
|
1754
|
+
},
|
|
1755
|
+
lifecycleEvents: AGENT_EVENT_KINDS,
|
|
1756
|
+
phases: ['idle', 'working', 'waiting', 'done'],
|
|
1757
|
+
indicators: ['spinner', 'pulse', 'dot', 'ring', 'badge', 'terminal', 'question'],
|
|
1758
|
+
tones: ['neutral', 'info', 'success', 'warning', 'danger', 'accent'],
|
|
1759
|
+
statusModel: {
|
|
1760
|
+
phase: 'Stable Termdock behavior used for notifications and attention.',
|
|
1761
|
+
id: 'Agent-defined fine-grained status referenced by hook mappings or agent-event.',
|
|
1762
|
+
label: 'Human-readable status text.',
|
|
1763
|
+
indicator: 'Theme-safe visual indicator.',
|
|
1764
|
+
tone: 'Theme token; arbitrary status colors are not accepted.',
|
|
1765
|
+
},
|
|
1766
|
+
migrationV1: {
|
|
1767
|
+
errorCode: 'AGENT_PLUGIN_MANIFEST_V1_UNSUPPORTED',
|
|
1768
|
+
guideCommand: 'td agent-plugin --json',
|
|
1769
|
+
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.',
|
|
1770
|
+
},
|
|
1771
|
+
hookPayload: {
|
|
1772
|
+
stdin: 'Optional JSON object.',
|
|
1773
|
+
recognizedFields: ['session_id', 'sessionId', 'message', 'cwd'],
|
|
1774
|
+
example: 'printf \'%s\' \'{"session_id":"abc","message":"Indexing"}\' | td agent-event my-agent prompt-submit thinking',
|
|
1775
|
+
},
|
|
1776
|
+
integrationSteps: [
|
|
1777
|
+
'Generate a version 2 manifest using this schema and save it as JSON.',
|
|
1778
|
+
'Run td plugin-create <manifest-file>; the Agent appears in Termdock immediately.',
|
|
1779
|
+
'Ask the user to open Settings > Detection Rules and click Install for this Agent. They can uninstall it there later.',
|
|
1780
|
+
'If the Agent hook format is not a Claude-style hooks map, configure native hooks to call td agent-event directly.',
|
|
1781
|
+
'Map the completed-turn hook to event "stop"; this is also the automatic-title trigger when the user enables titles for the Agent.',
|
|
1782
|
+
'Optionally declare titleNamer so this Agent can generate titles and dynamically publish its own model catalog.',
|
|
1783
|
+
'Run td plugin-list to verify registration.',
|
|
1784
|
+
],
|
|
1785
|
+
manifest: AGENT_PLUGIN_EXAMPLE,
|
|
1786
|
+
}, null, 2));
|
|
1787
|
+
}
|
|
1788
|
+
function runAgentEvent(input) {
|
|
1789
|
+
const slugPattern = /^[a-z][a-z0-9-]{0,39}$/;
|
|
1790
|
+
if (!slugPattern.test(input.slug)) {
|
|
1791
|
+
console.error(`${ICON.err} ${c.red('agent-event slug must be lowercase alphanumeric with hyphens')}`);
|
|
1792
|
+
process.exit(1);
|
|
1793
|
+
}
|
|
1794
|
+
if (!AGENT_EVENT_KINDS.includes(input.event)) {
|
|
1795
|
+
console.error(`${ICON.err} ${c.red(`Unknown agent event: ${input.event}`)}`);
|
|
1796
|
+
console.error(` ${c.dim(`Expected one of: ${AGENT_EVENT_KINDS.join(', ')}`)}`);
|
|
1797
|
+
process.exit(1);
|
|
1798
|
+
}
|
|
1799
|
+
if (input.status && !slugPattern.test(input.status)) {
|
|
1800
|
+
console.error(`${ICON.err} ${c.red('agent-event status must be lowercase alphanumeric with hyphens')}`);
|
|
1801
|
+
process.exit(1);
|
|
1802
|
+
}
|
|
1803
|
+
const script = fileURLToPath(new URL('./agentHook.js', import.meta.url));
|
|
1804
|
+
const result = spawnSync(process.execPath, [script, 'agent-hook', input.slug, input.event, ...(input.status ? [input.status] : [])], {
|
|
1805
|
+
stdio: 'inherit',
|
|
1806
|
+
env: process.env,
|
|
1807
|
+
});
|
|
1808
|
+
if (result.error) {
|
|
1809
|
+
console.error(`${ICON.err} ${c.red(`Failed to emit Agent event: ${result.error.message}`)}`);
|
|
1810
|
+
process.exit(1);
|
|
1811
|
+
}
|
|
1812
|
+
process.exit(result.status ?? 0);
|
|
1813
|
+
}
|
|
1667
1814
|
function runPluginInit() {
|
|
1668
1815
|
console.log(`\
|
|
1669
|
-
${c.bold('Agent
|
|
1670
|
-
${c.dim('
|
|
1816
|
+
${c.bold('Termdock Agent Integration Protocol v2')}
|
|
1817
|
+
${c.dim('──────────────────────────────────────')}
|
|
1818
|
+
|
|
1819
|
+
Machine-readable form for an Agent to inspect itself:
|
|
1820
|
+
|
|
1821
|
+
${c.cyan('td agent-plugin --json')}
|
|
1671
1822
|
|
|
1672
1823
|
A plugin is a directory under ${c.cyan('~/.termdock/agent-plugins/<slug>/')} containing:
|
|
1673
1824
|
|
|
@@ -1678,8 +1829,8 @@ ${c.bold('manifest.json schema')}
|
|
|
1678
1829
|
${c.dim('───────────────────')}
|
|
1679
1830
|
|
|
1680
1831
|
{
|
|
1681
|
-
${c.dim('// Manifest version. Must be')} ${c.green('
|
|
1682
|
-
"version":
|
|
1832
|
+
${c.dim('// Manifest version. Must be')} ${c.green('2')}${c.dim('.')}
|
|
1833
|
+
"version": 2,
|
|
1683
1834
|
|
|
1684
1835
|
${c.dim('// Unique slug (lowercase alphanum + hyphens, max 40 chars).')}
|
|
1685
1836
|
${c.dim('// Must not collide with built-in agent slugs.')}
|
|
@@ -1702,6 +1853,13 @@ ${c.dim('───────────────────')}
|
|
|
1702
1853
|
${c.dim('// "native" - SVG rendered directly, preserving original fills/gradients.')}
|
|
1703
1854
|
"iconMode": "native",
|
|
1704
1855
|
|
|
1856
|
+
${c.dim('// Optional fine-grained states. phase drives behavior; the rest drives UI.')}
|
|
1857
|
+
"statuses": [
|
|
1858
|
+
{ "id": "thinking", "phase": "working", "label": "Thinking", "indicator": "spinner", "tone": "info" },
|
|
1859
|
+
{ "id": "approval", "phase": "waiting", "label": "Needs approval", "indicator": "question", "tone": "warning" },
|
|
1860
|
+
{ "id": "complete", "phase": "done", "label": "Complete", "indicator": "badge", "tone": "success" }
|
|
1861
|
+
],
|
|
1862
|
+
|
|
1705
1863
|
${c.dim('// ── optional: hook installation (bring your own hooks.json surface) ──')}
|
|
1706
1864
|
"hooks": {
|
|
1707
1865
|
${c.dim("// Path to the agent's hooks config file (~-abbreviated).")}
|
|
@@ -1712,10 +1870,10 @@ ${c.dim('───────────────────')}
|
|
|
1712
1870
|
${c.dim('// Optional matcher narrows the subscription (e.g. permission type).')}
|
|
1713
1871
|
"events": [
|
|
1714
1872
|
{ "hook": "SessionStart", "event": "session-start" },
|
|
1715
|
-
{ "hook": "UserPromptSubmit", "event": "prompt-submit" },
|
|
1716
|
-
{ "hook": "Notification", "event": "notification", "matcher": "elicitation_dialog" },
|
|
1873
|
+
{ "hook": "UserPromptSubmit", "event": "prompt-submit", "status": "thinking" },
|
|
1874
|
+
{ "hook": "Notification", "event": "notification", "status": "approval", "matcher": "elicitation_dialog" },
|
|
1717
1875
|
{ "hook": "PostToolUse", "event": "tool-complete" },
|
|
1718
|
-
{ "hook": "Stop", "event": "stop" },
|
|
1876
|
+
{ "hook": "Stop", "event": "stop", "status": "complete" },
|
|
1719
1877
|
{ "hook": "SessionEnd", "event": "session-end" }
|
|
1720
1878
|
]
|
|
1721
1879
|
},
|
|
@@ -1726,6 +1884,15 @@ ${c.dim('───────────────────')}
|
|
|
1726
1884
|
"command": "trae --resume {sessionId}",
|
|
1727
1885
|
${c.dim('// Flags to strip from the original launch argv when replaying the resume.')}
|
|
1728
1886
|
"staleFlags": ["--resume", "-r", "--session-id", "--continue"]
|
|
1887
|
+
},
|
|
1888
|
+
|
|
1889
|
+
${c.dim('// ── optional: injectable automatic-title provider ──')}
|
|
1890
|
+
"titleNamer": {
|
|
1891
|
+
${c.dim('// No shell expansion. {prompt} is required; a bare {model} arg is omitted when automatic.')}
|
|
1892
|
+
"command": "trae",
|
|
1893
|
+
"args": ["title", "--prompt", "{prompt}", "--model={model}"],
|
|
1894
|
+
${c.dim('// Must print JSON: strings or {id, displayName, description, isDefault} objects.')}
|
|
1895
|
+
"models": { "command": "trae", "args": ["models", "--json"] }
|
|
1729
1896
|
}
|
|
1730
1897
|
}
|
|
1731
1898
|
|
|
@@ -1739,33 +1906,54 @@ ${c.dim('─────────────────────')}
|
|
|
1739
1906
|
tool-complete A tool execution completed (unblocks waiting state)
|
|
1740
1907
|
notification Generic notification from the agent (heuristic gated)
|
|
1741
1908
|
stop Agent turn finished / went idle
|
|
1909
|
+
Also triggers automatic title generation when enabled
|
|
1742
1910
|
session-end Agent session closed / binary exited
|
|
1743
1911
|
|
|
1744
1912
|
${c.bold('Creating a plugin')}
|
|
1745
1913
|
${c.dim('──────────────────')}
|
|
1746
1914
|
|
|
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')}
|
|
1915
|
+
${c.dim('# Inject the Agent definition; it appears in the UI immediately')}
|
|
1755
1916
|
td plugin-create ./trae-manifest.json
|
|
1756
1917
|
|
|
1757
|
-
${c.dim('#
|
|
1918
|
+
${c.dim('# The user then opens Settings > Detection Rules and clicks Install.')}
|
|
1919
|
+
${c.dim('# The same UI button can uninstall the hooks later.')}
|
|
1920
|
+
|
|
1921
|
+
${c.dim('# Verify registration')}
|
|
1758
1922
|
td plugin-list
|
|
1759
1923
|
|
|
1760
|
-
${c.dim('#
|
|
1924
|
+
${c.dim('# Remove')}
|
|
1761
1925
|
td plugin-remove trae
|
|
1762
1926
|
|
|
1927
|
+
${c.bold('Direct event bridge for any hook system')}
|
|
1928
|
+
${c.dim('───────────────────────────────────────')}
|
|
1929
|
+
|
|
1930
|
+
If the Agent does not use a Claude-style hooks JSON file, configure its
|
|
1931
|
+
native hooks to invoke this stable public CLI command instead:
|
|
1932
|
+
|
|
1933
|
+
td agent-event trae prompt-submit thinking
|
|
1934
|
+
td agent-event trae permission-request approval
|
|
1935
|
+
td agent-event trae stop complete
|
|
1936
|
+
|
|
1937
|
+
Hook JSON may be piped on stdin. Termdock recognizes session_id/sessionId,
|
|
1938
|
+
message, and cwd:
|
|
1939
|
+
|
|
1940
|
+
printf '%s' '{"session_id":"abc","message":"Indexing"}' | td agent-event trae prompt-submit thinking
|
|
1941
|
+
|
|
1763
1942
|
${c.bold('Manifest validation at startup')}
|
|
1764
1943
|
${c.dim('───────────────────────────────')}
|
|
1765
1944
|
|
|
1766
|
-
Termdock validates every plugin manifest
|
|
1945
|
+
Termdock validates every plugin manifest before registration. Errors are logged
|
|
1767
1946
|
to ${c.cyan('~/.termdock/server.log')} and visible via ${c.cyan('td plugin-list')}${c.dim('.')}
|
|
1768
1947
|
Slugs/aliases that collide with built-in agents are skipped.
|
|
1948
|
+
|
|
1949
|
+
${c.bold('Migrating a v1 plugin')}
|
|
1950
|
+
${c.dim('─────────────────────')}
|
|
1951
|
+
|
|
1952
|
+
v1 manifests fail with ${c.cyan('AGENT_PLUGIN_MANIFEST_V1_UNSUPPORTED')} and an
|
|
1953
|
+
AI-ready repair prompt. Copy that prompt together with the old manifest to
|
|
1954
|
+
an AI, or let the AI inspect the current schema directly:
|
|
1955
|
+
|
|
1956
|
+
${c.cyan('td agent-plugin --json')}
|
|
1769
1957
|
`);
|
|
1770
1958
|
}
|
|
1771
1959
|
async function runPluginCreate(manifestPath) {
|
|
@@ -1790,12 +1978,20 @@ async function runPluginCreate(manifestPath) {
|
|
|
1790
1978
|
const response = await postLocalJson(baseUrl, token, '/api/terminal/agent-plugins', manifest);
|
|
1791
1979
|
const body = JSON.parse(response.body || '{}');
|
|
1792
1980
|
if (response.statusCode < 200 || response.statusCode >= 300) {
|
|
1793
|
-
console.error(`${ICON.err} ${c.red(body.
|
|
1981
|
+
console.error(`${ICON.err} ${c.red(body.code ? `${body.code}:` : 'Plugin validation failed:')}`);
|
|
1982
|
+
console.error(body.error || 'Failed to create plugin');
|
|
1983
|
+
if (body.migration?.guideCommand) {
|
|
1984
|
+
console.error(`\n${c.bold('Current schema:')} ${c.cyan(body.migration.guideCommand)}`);
|
|
1985
|
+
}
|
|
1986
|
+
if (body.migration?.aiPrompt) {
|
|
1987
|
+
console.error(`\n${c.bold('Copy this to your AI:')}\n${body.migration.aiPrompt}`);
|
|
1988
|
+
}
|
|
1794
1989
|
process.exit(1);
|
|
1795
1990
|
}
|
|
1796
1991
|
console.log(`${ICON.ok} ${c.green(`Plugin "${body.slug}" created.`)}`);
|
|
1797
1992
|
console.log(` ${c.dim('Directory:')} ${c.cyan(body.dir ?? resolved)}`);
|
|
1798
|
-
console.log(` ${c.dim('
|
|
1993
|
+
console.log(` ${c.dim('Visible immediately in Settings > Detection Rules; no restart required.')}`);
|
|
1994
|
+
console.log(` ${c.dim('The user can click Install or Uninstall there.')}`);
|
|
1799
1995
|
}
|
|
1800
1996
|
async function runPluginList() {
|
|
1801
1997
|
const { baseUrl, token } = requireRunningServer();
|
|
@@ -1808,7 +2004,7 @@ async function runPluginList() {
|
|
|
1808
2004
|
const plugins = body.plugins ?? [];
|
|
1809
2005
|
if (plugins.length === 0) {
|
|
1810
2006
|
console.log(`${c.dim('No plugins installed.')}`);
|
|
1811
|
-
console.log(` ${c.dim('See:')} ${c.cyan('td plugin
|
|
2007
|
+
console.log(` ${c.dim('See:')} ${c.cyan('td agent-plugin')}`);
|
|
1812
2008
|
}
|
|
1813
2009
|
else {
|
|
1814
2010
|
console.log(`${c.bold('Installed plugins')} ${c.dim(`(${plugins.length})`)}`);
|
|
@@ -1831,7 +2027,13 @@ async function runPluginList() {
|
|
|
1831
2027
|
console.log('');
|
|
1832
2028
|
console.log(`${c.yellow('Validation errors')}`);
|
|
1833
2029
|
for (const e of errors) {
|
|
1834
|
-
console.log(` ${c.red('✗')} ${c.cyan(e.slug)}${c.dim(':')} ${e.errors.join('; ')}`);
|
|
2030
|
+
console.log(` ${c.red('✗')} ${c.cyan(e.slug)}${c.dim(':')} ${e.code ? c.yellow(e.code) : e.errors.join('; ')}`);
|
|
2031
|
+
for (const message of e.errors)
|
|
2032
|
+
console.log(` ${message}`);
|
|
2033
|
+
if (e.migration?.guideCommand)
|
|
2034
|
+
console.log(` ${c.dim('Current schema:')} ${c.cyan(e.migration.guideCommand)}`);
|
|
2035
|
+
if (e.migration?.aiPrompt)
|
|
2036
|
+
console.log(` ${c.dim('Copy this to your AI:')} ${e.migration.aiPrompt}`);
|
|
1835
2037
|
}
|
|
1836
2038
|
}
|
|
1837
2039
|
}
|
|
@@ -2809,8 +3011,14 @@ async function main() {
|
|
|
2809
3011
|
runClearPassword();
|
|
2810
3012
|
process.exit(0);
|
|
2811
3013
|
}
|
|
3014
|
+
if (options.agentEvent) {
|
|
3015
|
+
runAgentEvent(options.agentEvent);
|
|
3016
|
+
}
|
|
2812
3017
|
if (options.pluginInit) {
|
|
2813
|
-
|
|
3018
|
+
if (options.pluginInitJson)
|
|
3019
|
+
runPluginInitJson();
|
|
3020
|
+
else
|
|
3021
|
+
runPluginInit();
|
|
2814
3022
|
process.exit(0);
|
|
2815
3023
|
}
|
|
2816
3024
|
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) {
|