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.
Files changed (84) hide show
  1. package/.desktop-dist/main.js +254 -49
  2. package/README.md +6 -0
  3. package/dist/client/assets/{ModelPreview-Bk8GM7G-.js → ModelPreview-Cs14IQuQ.js} +1 -1
  4. package/dist/client/assets/{abnfDiagram-VRR7QNED-BYiQXGYk.js → abnfDiagram-VRR7QNED-DuDfdv-6.js} +1 -1
  5. package/dist/client/assets/{arc-jlT_jmoZ.js → arc-CSYzGV_2.js} +1 -1
  6. package/dist/client/assets/{architectureDiagram-ZJ3FMSHR-D1qZ875Q.js → architectureDiagram-ZJ3FMSHR-YiZ0HXNi.js} +1 -1
  7. package/dist/client/assets/{blockDiagram-677ZJIJ3-qkgn2Ysc.js → blockDiagram-677ZJIJ3-IaQMzmYv.js} +1 -1
  8. package/dist/client/assets/{c4Diagram-LMCZKHZV-JgcH49Pf.js → c4Diagram-LMCZKHZV-BwyuIPRL.js} +1 -1
  9. package/dist/client/assets/channel-BotxtiNE.js +1 -0
  10. package/dist/client/assets/{chunk-2Q5K7J3B-E-3iflEy.js → chunk-2Q5K7J3B-BtHQYLBT.js} +1 -1
  11. package/dist/client/assets/{chunk-32BRIVSS-mM01y3kF.js → chunk-32BRIVSS-CXrK07sW.js} +1 -1
  12. package/dist/client/assets/{chunk-5VM5RSS4-GO8Muo5l.js → chunk-5VM5RSS4-BfBshn11.js} +1 -1
  13. package/dist/client/assets/{chunk-EX3LRPZG-BUSDluH0.js → chunk-EX3LRPZG-BJbilNaf.js} +1 -1
  14. package/dist/client/assets/{chunk-JWPE2WC7-obSL6EzJ.js → chunk-JWPE2WC7-wgcmSRlL.js} +1 -1
  15. package/dist/client/assets/{chunk-MOJQB5TN-zEktbtAG.js → chunk-MOJQB5TN-8VZVUeDI.js} +1 -1
  16. package/dist/client/assets/{chunk-RYQCIY6F-kH7H8XgQ.js → chunk-RYQCIY6F-B900YBFa.js} +1 -1
  17. package/dist/client/assets/{chunk-V7JOEXUC-BqDFpHKN.js → chunk-V7JOEXUC-BK4_RLz-.js} +1 -1
  18. package/dist/client/assets/{chunk-VR4S4FIN-BGZ7kCIG.js → chunk-VR4S4FIN-Kj5_ycjB.js} +1 -1
  19. package/dist/client/assets/{chunk-XXDRQBXY-D-TatWXe.js → chunk-XXDRQBXY-BolvPLRz.js} +1 -1
  20. package/dist/client/assets/classDiagram-OUVF2IWQ-CLWlbTSC.js +1 -0
  21. package/dist/client/assets/classDiagram-v2-EOCWNBFH-CLWlbTSC.js +1 -0
  22. package/dist/client/assets/{cose-bilkent-JH36ORCC-CfWyrViz.js → cose-bilkent-JH36ORCC-9_9_SLqv.js} +1 -1
  23. package/dist/client/assets/{cynefin-VYW2F7L2-BFdZ3FcE.js → cynefin-VYW2F7L2-yEpoi3vG.js} +1 -1
  24. package/dist/client/assets/{cynefinDiagram-TSTJHNR4-iAfCVoFU.js → cynefinDiagram-TSTJHNR4-Dy-gvup_.js} +1 -1
  25. package/dist/client/assets/{dagre-VKFMJZFB-CZhh0hrr.js → dagre-VKFMJZFB-CNjJ5pRK.js} +1 -1
  26. package/dist/client/assets/{diagram-FQU43EPY-BqUK2OmV.js → diagram-FQU43EPY-uZ73Rj3T.js} +1 -1
  27. package/dist/client/assets/{diagram-G47NLZAW-vC0pSQYC.js → diagram-G47NLZAW-Cxt7TYZT.js} +1 -1
  28. package/dist/client/assets/{diagram-NH7WQ7WH-Dqc5oxed.js → diagram-NH7WQ7WH-B4TKinuZ.js} +1 -1
  29. package/dist/client/assets/{diagram-OA4YK3LP-DIIXckFE.js → diagram-OA4YK3LP-CGSuenHU.js} +1 -1
  30. package/dist/client/assets/{diagram-WEI45ONY-DR9_cFeh.js → diagram-WEI45ONY-DqqzDaUZ.js} +1 -1
  31. package/dist/client/assets/{ebnfDiagram-CCIWWBDH-21OO0OXC.js → ebnfDiagram-CCIWWBDH-B2SgXL5w.js} +1 -1
  32. package/dist/client/assets/{erDiagram-Q63AITRT-YKnVG3az.js → erDiagram-Q63AITRT-D-ibkr8y.js} +1 -1
  33. package/dist/client/assets/{flowDiagram-23GEKE2U-Cm7TGErF.js → flowDiagram-23GEKE2U-BPeyMqr-.js} +1 -1
  34. package/dist/client/assets/{ganttDiagram-NO4QXBWP-D_76Effz.js → ganttDiagram-NO4QXBWP-DcEqEzzu.js} +1 -1
  35. package/dist/client/assets/{gitGraphDiagram-IHSO6WYX-CC1ysmVD.js → gitGraphDiagram-IHSO6WYX-DTZ6UCQY.js} +1 -1
  36. package/dist/client/assets/index-DhfIiBLW.css +1 -0
  37. package/dist/client/assets/index-ivnGnbVg.js +451 -0
  38. package/dist/client/assets/{infoDiagram-FWYZ7A6U-BbgpmhBz.js → infoDiagram-FWYZ7A6U-BHrzVXeX.js} +1 -1
  39. package/dist/client/assets/{ishikawaDiagram-FXEZZL3T-C94bY_je.js → ishikawaDiagram-FXEZZL3T-CwuoQJku.js} +1 -1
  40. package/dist/client/assets/{journeyDiagram-5HDEW3XC-BQ0iFRxs.js → journeyDiagram-5HDEW3XC-ZEcy0rDZ.js} +1 -1
  41. package/dist/client/assets/{kanban-definition-HUTT4EX6-BtAexXPo.js → kanban-definition-HUTT4EX6-BDBawNbr.js} +1 -1
  42. package/dist/client/assets/{linear-C2HnIdOK.js → linear-smYQWzDV.js} +1 -1
  43. package/dist/client/assets/{mermaid.core-BbG4sJBF.js → mermaid.core-eJs7Ouvw.js} +4 -4
  44. package/dist/client/assets/{mindmap-definition-LN4V7U3C-B-uvViws.js → mindmap-definition-LN4V7U3C-BK1IZ9zg.js} +1 -1
  45. package/dist/client/assets/{pegDiagram-2B236MQR-DHYcR6tE.js → pegDiagram-2B236MQR-B3-mVNgX.js} +1 -1
  46. package/dist/client/assets/{pieDiagram-ENE6RG2P-DEE9cD6M.js → pieDiagram-ENE6RG2P-Bkeeyx5l.js} +1 -1
  47. package/dist/client/assets/{quadrantDiagram-ABIIQ3AL-DMQP5jG3.js → quadrantDiagram-ABIIQ3AL-DouIklEO.js} +1 -1
  48. package/dist/client/assets/{railroadDiagram-RFXS5EU6-B-4WDTwc.js → railroadDiagram-RFXS5EU6-BB_jXQd2.js} +1 -1
  49. package/dist/client/assets/{requirementDiagram-TGXJPOKE-C2xw_q-R.js → requirementDiagram-TGXJPOKE-u56IHfiR.js} +1 -1
  50. package/dist/client/assets/{sankeyDiagram-HTMAVEWB-wq0BPqhc.js → sankeyDiagram-HTMAVEWB-eDi2cs5E.js} +1 -1
  51. package/dist/client/assets/{sequenceDiagram-DBY2YBRQ-BqpM0A9d.js → sequenceDiagram-DBY2YBRQ-DnAA2xYa.js} +1 -1
  52. package/dist/client/assets/{sizeCapture-X5ZJPWSS-Ba2xxyp_.js → sizeCapture-X5ZJPWSS-BogPui79.js} +1 -1
  53. package/dist/client/assets/{stateDiagram-2N3HPSRC-agPx5qAN.js → stateDiagram-2N3HPSRC--EeXDy2M.js} +1 -1
  54. package/dist/client/assets/stateDiagram-v2-6OUMAXLB-Dc-3HKTs.js +1 -0
  55. package/dist/client/assets/{swimlanes-5IMT3BWC-Cy6Lzj_L.js → swimlanes-5IMT3BWC-DZAw7-kY.js} +2 -2
  56. package/dist/client/assets/swimlanesDiagram-G3AALYLV-CI1Wlr0V.js +8 -0
  57. package/dist/client/assets/{timeline-definition-FHXFAJF6-LBrVfbdp.js → timeline-definition-FHXFAJF6-DkcnpB-Y.js} +1 -1
  58. package/dist/client/assets/{vennDiagram-L72KCM5P-BiYIV7mX.js → vennDiagram-L72KCM5P-BStcDGEf.js} +1 -1
  59. package/dist/client/assets/{wardleyDiagram-EHGQE667-Q1QU9gG3.js → wardleyDiagram-EHGQE667-Cj4CQn1M.js} +1 -1
  60. package/dist/client/assets/{xychartDiagram-FW5EYKEG-CzYpxk4l.js → xychartDiagram-FW5EYKEG-_uBCjai8.js} +1 -1
  61. package/dist/client/index.html +2 -2
  62. package/dist/client/sw.js +1 -1
  63. package/dist/server/agent/autoTitle.js +118 -0
  64. package/dist/server/agent/installers.js +11 -10
  65. package/dist/server/agent/plugins.js +90 -4
  66. package/dist/server/agent/registry.js +1 -0
  67. package/dist/server/agent/session.js +18 -2
  68. package/dist/server/agent/titleNamerCatalog.js +203 -0
  69. package/dist/server/agentHook.js +2 -1
  70. package/dist/server/cli.js +218 -30
  71. package/dist/server/routes/filesystem.js +11 -8
  72. package/dist/server/routes/terminal.js +351 -10
  73. package/dist/server/utils/autoUpdate.js +175 -0
  74. package/dist/server/utils/npmUpdate.js +2 -1
  75. package/dist/server/utils/settings.js +44 -0
  76. package/package.json +1 -1
  77. package/runtime-manifest.json +2 -2
  78. package/dist/client/assets/channel-C2i7H7bH.js +0 -1
  79. package/dist/client/assets/classDiagram-OUVF2IWQ-BlGG6XFj.js +0 -1
  80. package/dist/client/assets/classDiagram-v2-EOCWNBFH-BlGG6XFj.js +0 -1
  81. package/dist/client/assets/index-ChhyERKX.css +0 -1
  82. package/dist/client/assets/index-r-PkH7SB.js +0 -442
  83. package/dist/client/assets/stateDiagram-v2-6OUMAXLB-beoQXpWJ.js +0 -1
  84. package/dist/client/assets/swimlanesDiagram-G3AALYLV-BxL1xNhC.js +0 -8
@@ -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 instead of a table
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 agent plugin manifest schema reference
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
- pi Same as --plugin-init
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
- argv = argv.slice(1);
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
- tlsJson = true;
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 Plugin Reference')}
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('1')}${c.dim('.')}
1682
- "version": 1,
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
- # 1. Create the plugin directory with manifest + icon
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('# 4. List installed plugins')}
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('# 5. Remove a plugin')}
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 on server start. Errors are logged
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.error || 'Failed to create plugin')}`);
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('Restart Termdock to activate:')} ${c.cyan('td --stop && td')}`);
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-init')}`);
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
- runPluginInit();
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: false, gitTimeoutMs: options.gitTimeoutMs }),
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: now + GIT_BUNDLE_CACHE_TTL_MS });
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: now + GIT_BUNDLE_CACHE_TTL_MS });
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
- gitBundleCache.set(cacheKey, { bundle, expiresAt: Date.now() + GIT_BUNDLE_CACHE_TTL_MS });
1152
- return bundle;
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
- gitBundleCache.set(cacheKey, { bundle, expiresAt: Date.now() + GIT_BUNDLE_CACHE_TTL_MS });
1170
- return bundle;
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) {