@0xmaxma/claude-gateway 1.3.24 → 1.3.31

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 (92) hide show
  1. package/README.md +172 -50
  2. package/config.template.json +6 -2
  3. package/dist/agent/incident-store.d.ts +89 -0
  4. package/dist/agent/incident-store.d.ts.map +1 -0
  5. package/dist/agent/incident-store.js +299 -0
  6. package/dist/agent/incident-store.js.map +1 -0
  7. package/dist/agent/incident.d.ts +156 -0
  8. package/dist/agent/incident.d.ts.map +1 -0
  9. package/dist/agent/incident.js +177 -0
  10. package/dist/agent/incident.js.map +1 -0
  11. package/dist/agent/recovery-executor.d.ts +117 -0
  12. package/dist/agent/recovery-executor.d.ts.map +1 -0
  13. package/dist/agent/recovery-executor.js +168 -0
  14. package/dist/agent/recovery-executor.js.map +1 -0
  15. package/dist/agent/recovery-policy.d.ts +97 -0
  16. package/dist/agent/recovery-policy.d.ts.map +1 -0
  17. package/dist/agent/recovery-policy.js +164 -0
  18. package/dist/agent/recovery-policy.js.map +1 -0
  19. package/dist/agent/runner.d.ts +44 -0
  20. package/dist/agent/runner.d.ts.map +1 -1
  21. package/dist/agent/runner.js +272 -2
  22. package/dist/agent/runner.js.map +1 -1
  23. package/dist/agent/safe-mode.d.ts +61 -0
  24. package/dist/agent/safe-mode.d.ts.map +1 -0
  25. package/dist/agent/safe-mode.js +102 -0
  26. package/dist/agent/safe-mode.js.map +1 -0
  27. package/dist/agent/triage.d.ts +94 -0
  28. package/dist/agent/triage.d.ts.map +1 -0
  29. package/dist/agent/triage.js +209 -0
  30. package/dist/agent/triage.js.map +1 -0
  31. package/dist/agent/turn-trace.d.ts +120 -0
  32. package/dist/agent/turn-trace.d.ts.map +1 -0
  33. package/dist/agent/turn-trace.js +122 -0
  34. package/dist/agent/turn-trace.js.map +1 -0
  35. package/dist/api/gateway-router.d.ts +21 -0
  36. package/dist/api/gateway-router.d.ts.map +1 -1
  37. package/dist/api/gateway-router.js +58 -17
  38. package/dist/api/gateway-router.js.map +1 -1
  39. package/dist/api/line-pending-senders.d.ts +7 -2
  40. package/dist/api/line-pending-senders.d.ts.map +1 -1
  41. package/dist/api/line-pending-senders.js +7 -2
  42. package/dist/api/line-pending-senders.js.map +1 -1
  43. package/dist/api/router.d.ts.map +1 -1
  44. package/dist/api/router.js +563 -172
  45. package/dist/api/router.js.map +1 -1
  46. package/dist/api/wizard-state.d.ts +1 -4
  47. package/dist/api/wizard-state.d.ts.map +1 -1
  48. package/dist/api/wizard-state.js.map +1 -1
  49. package/dist/config/migrator.d.ts +4 -0
  50. package/dist/config/migrator.d.ts.map +1 -1
  51. package/dist/config/migrator.js +60 -3
  52. package/dist/config/migrator.js.map +1 -1
  53. package/dist/discord/receiver.d.ts +1 -0
  54. package/dist/discord/receiver.d.ts.map +1 -1
  55. package/dist/discord/receiver.js +24 -6
  56. package/dist/discord/receiver.js.map +1 -1
  57. package/dist/index.js +3 -0
  58. package/dist/index.js.map +1 -1
  59. package/dist/session/process.d.ts +18 -0
  60. package/dist/session/process.d.ts.map +1 -1
  61. package/dist/session/process.js +61 -1
  62. package/dist/session/process.js.map +1 -1
  63. package/dist/shell/claude-pty-shell.js +59 -0
  64. package/dist/shell/claude-pty-shell.js.map +1 -1
  65. package/dist/shell/control-channel.d.ts +74 -0
  66. package/dist/shell/control-channel.d.ts.map +1 -0
  67. package/dist/shell/control-channel.js +114 -0
  68. package/dist/shell/control-channel.js.map +1 -0
  69. package/dist/telegram/receiver.d.ts +1 -0
  70. package/dist/telegram/receiver.d.ts.map +1 -1
  71. package/dist/telegram/receiver.js +18 -6
  72. package/dist/telegram/receiver.js.map +1 -1
  73. package/dist/types.d.ts +21 -0
  74. package/dist/types.d.ts.map +1 -1
  75. package/dist/ui/web-ui.d.ts.map +1 -1
  76. package/dist/ui/web-ui.js +103 -2
  77. package/dist/ui/web-ui.js.map +1 -1
  78. package/mcp/tools/discord/access.ts +136 -20
  79. package/mcp/tools/discord/client.ts +3 -1
  80. package/mcp/tools/discord/module.ts +75 -4
  81. package/mcp/tools/discord/skills/access/SKILL.md +66 -8
  82. package/mcp/tools/discord/skills/configure/SKILL.md +11 -1
  83. package/mcp/tools/discord/types.ts +21 -2
  84. package/mcp/tools/skills/handlers.ts +4 -2
  85. package/mcp/tools/telegram/dedup.ts +4 -1
  86. package/mcp/tools/telegram/module.ts +5 -14
  87. package/mcp/tools/telegram/pure.ts +169 -31
  88. package/mcp/tools/telegram/receiver-server.ts +271 -78
  89. package/mcp/tools/telegram/skills/access/SKILL.md +93 -23
  90. package/mcp/tools/telegram/skills/configure/SKILL.md +31 -26
  91. package/mcp/tools/telegram/typing.ts +124 -0
  92. package/package.json +3 -1
@@ -39,6 +39,9 @@ const path = __importStar(require("path"));
39
39
  const logger_1 = require("../logger");
40
40
  const AUTO_RESTART_DELAY_MS = 5000;
41
41
  const MAX_RESTARTS = 3;
42
+ // After MAX_RESTARTS fast attempts, fall back to a slow indefinite retry
43
+ // instead of giving up permanently — mirrors discord/receiver.ts.
44
+ const SLOW_RESTART_DELAY_MS = 5 * 60000;
42
45
  class TelegramReceiver {
43
46
  constructor(agentConfig, callbackPort, logDir) {
44
47
  this.agentConfig = agentConfig;
@@ -47,12 +50,14 @@ class TelegramReceiver {
47
50
  this.process = null;
48
51
  this.stopping = false;
49
52
  this.restartCount = 0;
53
+ this.slowPhaseAnnounced = false;
50
54
  this.restartTimer = null;
51
55
  this.logger = (0, logger_1.createLogger)(`${agentConfig.id}:receiver`, logDir);
52
56
  }
53
57
  start() {
54
58
  this.stopping = false;
55
59
  this.restartCount = 0;
60
+ this.slowPhaseAnnounced = false;
56
61
  this.spawnProcess();
57
62
  }
58
63
  spawnProcess() {
@@ -80,19 +85,26 @@ class TelegramReceiver {
80
85
  this.logger.info('TelegramReceiver started');
81
86
  }
82
87
  scheduleRestart() {
83
- if (this.restartCount >= MAX_RESTARTS) {
84
- this.logger.error('TelegramReceiver max restarts reached');
85
- return;
88
+ const slowPhase = this.restartCount >= MAX_RESTARTS;
89
+ const delay = slowPhase ? SLOW_RESTART_DELAY_MS : AUTO_RESTART_DELAY_MS;
90
+ if (!slowPhase)
91
+ this.restartCount++;
92
+ // Surface the transition into indefinite slow-retry once at error level so a
93
+ // permanently-broken config (bad/revoked token) is visible instead of hiding
94
+ // behind a steady stream of warn-level retry lines.
95
+ if (slowPhase && !this.slowPhaseAnnounced) {
96
+ this.slowPhaseAnnounced = true;
97
+ this.logger.error(`TelegramReceiver failed ${MAX_RESTARTS} fast restarts; switching to slow retry every ${SLOW_RESTART_DELAY_MS}ms — check the bot token`);
86
98
  }
87
- this.restartCount++;
88
- this.logger.warn(`Restarting TelegramReceiver in ${AUTO_RESTART_DELAY_MS}ms`, {
99
+ this.logger.warn(`Restarting TelegramReceiver in ${delay}ms`, {
89
100
  attempt: this.restartCount,
101
+ slowPhase,
90
102
  });
91
103
  this.restartTimer = setTimeout(() => {
92
104
  this.restartTimer = null;
93
105
  if (!this.stopping)
94
106
  this.spawnProcess();
95
- }, AUTO_RESTART_DELAY_MS);
107
+ }, delay);
96
108
  }
97
109
  stop() {
98
110
  this.stopping = true;
@@ -1 +1 @@
1
- {"version":3,"file":"receiver.js","sourceRoot":"","sources":["../../src/telegram/receiver.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"receiver.js","sourceRoot":"","sources":["../../src/telegram/receiver.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -136,6 +136,14 @@ export interface GatewayConfig {
136
136
  gateway: {
137
137
  logDir: string;
138
138
  timezone: string;
139
+ /**
140
+ * Network interface the HTTP/WebSocket server binds to. Defaults to
141
+ * "127.0.0.1" (localhost-only) so the dashboard and API are not exposed to
142
+ * the local network out of the box. Set to "0.0.0.0" to expose all
143
+ * interfaces (e.g. behind a trusted reverse proxy). The `GATEWAY_BIND` env
144
+ * var, when set, takes precedence over this field.
145
+ */
146
+ bind?: string;
139
147
  models?: ModelConfig[];
140
148
  api?: {
141
149
  keys: ApiKey[];
@@ -145,6 +153,19 @@ export interface GatewayConfig {
145
153
  * false = interactive backend: claude TUI under the claude-pty-shell PTY wrapper.
146
154
  */
147
155
  headless?: boolean;
156
+ /**
157
+ * Self-healing recovery (Epic #195, Phase 3). When `autoRecover` is true the
158
+ * turn-trace watchdog may auto-execute a whitelisted recovery action
159
+ * (keystroke, session/receiver restart, safe-mode fallback, guarded resend)
160
+ * for a stalled turn. Default false — detection, incident logging, and
161
+ * notification still run when disabled; only live action execution is gated,
162
+ * so the feature ships dark and the operator opts in when ready. Safe-mode
163
+ * auto-fallback on hard PTY failure is independent of this flag (it is always
164
+ * reversible and never presses keys).
165
+ */
166
+ selfHealing?: {
167
+ autoRecover?: boolean;
168
+ };
148
169
  /** Global history retention/cleanup defaults */
149
170
  history?: HistoryConfig & {
150
171
  cleanupHour?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,6FAA6F;IAC7F,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,4EAA4E;IAC5E,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,iEAAiE;IACjE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE;QACT,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,OAAO,CAAC,EAAE;QACR,QAAQ,EAAE,MAAM,CAAC;QACjB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;QAC1B,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;QAC5B,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAC;QAC7C,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;QACvB,UAAU,CAAC,EAAE,OAAO,CAAC;KACtB,CAAC;IACF,IAAI,CAAC,EAAE;QACL,kBAAkB,EAAE,MAAM,CAAC;QAC3B,aAAa,EAAE,MAAM,CAAC;QACtB;;;;;;WAMG;QACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;QAC/B,iEAAiE;QACjE,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,6EAA6E;QAC7E,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB;;;;;WAKG;QACH,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAC;QAC7C,wFAAwF;QACxF,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;QACvB;;;;;;WAMG;QACH,WAAW,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAC;QAChD;;;;WAIG;QACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;QAC1B;;;;WAIG;QACH,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB;;;;;;;;;WASG;QACH,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC;IACF,MAAM,EAAE;QACN,KAAK,EAAE,MAAM,CAAC;QACd,8FAA8F;QAC9F,0BAA0B,CAAC,EAAE,OAAO,CAAC;QACrC,UAAU,EAAE,MAAM,EAAE,CAAC;KACtB,CAAC;IACF,gCAAgC;IAChC,SAAS,CAAC,EAAE;QACV,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,CAAC;IACF,4BAA4B;IAC5B,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,4DAA4D;IAC5D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,4GAA4G;IAC5G,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,2CAA2C;IAC3C,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,kFAAkF;IAClF,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,OAAO,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,IAAI,IAAI,CAAC;IACd,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;CACtB;AAED,MAAM,WAAW,MAAM;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE;QACP,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB;;;;;;WAMG;QACH,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;QACvB,GAAG,CAAC,EAAE;YACJ,IAAI,EAAE,MAAM,EAAE,CAAC;SAChB,CAAC;QACF;;;WAGG;QACH,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB;;;;;;;;;WASG;QACH,WAAW,CAAC,EAAE;YACZ,WAAW,CAAC,EAAE,OAAO,CAAC;SACvB,CAAC;QACF,gDAAgD;QAChD,OAAO,CAAC,EAAE,aAAa,GAAG;YACxB,WAAW,CAAC,EAAE,MAAM,CAAC;YACrB,eAAe,CAAC,EAAE,MAAM,CAAC;SAC1B,CAAC;KACH,CAAC;IACF,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAElB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,cAAc,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,UAAU,EAAE,aAAa,CAAC;CAClD;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,YAAY;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,WAAW,EAAE,CAAC;CACzB;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAC/E;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,aAAa,EAAE,CAAA;CAAE,GAC/D;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvC,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC7D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC9D;AAID,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,IAAI,CAAC;AAC7C,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AAE9C,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,IAAI,GAAG,OAAO,GAAG,IAAI,CAAC;IAClC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IAEb,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,YAAY,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IAEb,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,IAAI,GAAG,OAAO,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}
@@ -1 +1 @@
1
- {"version":3,"file":"web-ui.d.ts","sourceRoot":"","sources":["../../src/ui/web-ui.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,SAAK,GAAG,MAAM,CAkxB5D"}
1
+ {"version":3,"file":"web-ui.d.ts","sourceRoot":"","sources":["../../src/ui/web-ui.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,SAAK,GAAG,MAAM,CAu3B5D"}
package/dist/ui/web-ui.js CHANGED
@@ -119,6 +119,22 @@ function generateDashboardHtml(dashToken = '') {
119
119
  }
120
120
  .pty-close:hover { color: #fc8181; }
121
121
  .pty-refresh:hover { color: #63b3ed; }
122
+ /* Keyboard-focus outline on the viewer (it is tabindex focusable so PageUp/
123
+ PageDown reach the terminal); keep it subtle rather than the default ring. */
124
+ #pty-terminal:focus, #pty-terminal:focus-visible { outline: 1px solid #2d3748; }
125
+ .pty-mode-toggle {
126
+ background: none;
127
+ border: 1px solid #2d3748;
128
+ border-radius: 4px;
129
+ color: #718096;
130
+ cursor: pointer;
131
+ font-size: 0.72rem;
132
+ padding: 1px 7px;
133
+ margin-right: 6px;
134
+ }
135
+ .pty-mode-toggle:hover { color: #63b3ed; border-color: #3d4a5f; }
136
+ /* Active (input) mode — clearly signals the viewer now types into the PTY. */
137
+ .pty-mode-toggle.input-active { color: #0d1117; background: #63b3ed; border-color: #63b3ed; font-weight: 600; }
122
138
  /* Fixed-size terminal viewport — the server PTY runs at 200x50, so the
123
139
  viewer must NOT resize to the panel (that mismatch is what garbles the
124
140
  output). We render at the native size and pan horizontally if the 200-col
@@ -233,13 +249,16 @@ function generateDashboardHtml(dashToken = '') {
233
249
  in view when streaming. -->
234
250
  <div class="pty-viewer" id="pty-viewer">
235
251
  <div class="pty-viewer-header">
236
- <span>Shell Process Viewer &mdash; <span class="agent-label" id="pty-agent-label"></span><span class="session-label" id="pty-session-label"></span></span>
252
+ <span><span id="pty-title-text">Terminal Viewer</span> &mdash; <span class="agent-label" id="pty-agent-label"></span><span class="session-label" id="pty-session-label"></span></span>
237
253
  <span>
254
+ <button class="pty-mode-toggle" id="pty-mode-toggle-btn" title="Toggle input mode (type into the live terminal)">&#x2328; View</button>
238
255
  <button class="pty-refresh" id="pty-refresh-btn" title="Refresh (reconnect &amp; redraw)">&#x21ba;</button>
239
256
  <button class="pty-close" id="pty-close-btn" title="Close">&#x2715;</button>
240
257
  </span>
241
258
  </div>
242
- <div id="pty-terminal"></div>
259
+ <!-- tabindex makes the viewer keyboard-focusable so PageUp/PageDown reach it
260
+ even in read-only view mode (xterm stdin is disabled there). -->
261
+ <div id="pty-terminal" tabindex="0"></div>
243
262
  </div>
244
263
 
245
264
  <!-- Sessions — full width (session-centric, flat list) -->
@@ -350,6 +369,11 @@ function generateDashboardHtml(dashToken = '') {
350
369
  // multi-byte char into noise — decode as UTF-8 with {stream:true} so sequences
351
370
  // split across WebSocket frames are reassembled instead of corrupted.
352
371
  let utf8Decoder = null;
372
+ // Interactive input mode (Issue #201). When on, keystrokes typed into the
373
+ // terminal are streamed to the live PTY over the same WebSocket. Off by
374
+ // default (read-only mirror); the viewer flips it on via the mode toggle.
375
+ let ptyInputMode = false;
376
+ let ptyOnDataDisposable = null;
353
377
 
354
378
  // The agent's TUI enables mouse tracking (DECSET 1000/1002/1003/1006...).
355
379
  // While those modes are active, xterm.js forwards wheel/click events to the
@@ -373,6 +397,9 @@ function generateDashboardHtml(dashToken = '') {
373
397
  // Append the session id after the agent name, e.g. "claude-founder · 3c01897c…".
374
398
  document.getElementById('pty-session-label').textContent = sessionId ? ' \\u00b7 ' + sessionId : '';
375
399
  document.getElementById('pty-viewer').style.display = 'block';
400
+ // Always (re)open a fresh session in read-only view mode; the mode toggle
401
+ // lets the viewer switch into interactive input on demand.
402
+ setPtyInputMode(false);
376
403
 
377
404
  if (!term) {
378
405
  term = new Terminal({
@@ -488,12 +515,53 @@ function generateDashboardHtml(dashToken = '') {
488
515
  // are about to close intentionally, so it does not schedule a new one.
489
516
  if (ptyReconnectTimer) { clearTimeout(ptyReconnectTimer); ptyReconnectTimer = null; }
490
517
  ptyReconnectAttempts = 0;
518
+ setPtyInputMode(false); // always leave input mode when closing/switching
491
519
  if (ptyWs) { ptyWs.onclose = null; ptyWs.onerror = null; ptyWs.close(); ptyWs = null; }
492
520
  currentPtyAgent = null;
493
521
  currentPtySession = null;
494
522
  document.getElementById('pty-viewer').style.display = 'none';
495
523
  }
496
524
 
525
+ // Switch the viewer between read-only mirror and interactive input. In input
526
+ // mode the terminal accepts stdin and every keystroke (printable chars,
527
+ // Enter, arrows, Ctrl-combos, Esc) is streamed to the live PTY over the WS,
528
+ // like a real terminal; the title and toggle reflect the active mode.
529
+ function setPtyInputMode(on) {
530
+ if (!term) on = false;
531
+ ptyInputMode = !!on;
532
+ const toggleBtn = document.getElementById('pty-mode-toggle-btn');
533
+ const titleEl = document.getElementById('pty-title-text');
534
+ if (term) term.options.disableStdin = !ptyInputMode;
535
+ // Detach any previous onData listener before (re)attaching, so toggling
536
+ // off — or toggling on twice — never leaves a duplicate keystroke stream.
537
+ if (ptyOnDataDisposable) { ptyOnDataDisposable.dispose(); ptyOnDataDisposable = null; }
538
+ if (ptyInputMode && term) {
539
+ ptyOnDataDisposable = term.onData(function(data) {
540
+ if (ptyWs && ptyWs.readyState === WebSocket.OPEN) ptyWs.send(data);
541
+ });
542
+ }
543
+ if (titleEl) titleEl.textContent = ptyInputMode ? 'Interactive Terminal' : 'Terminal Viewer';
544
+ if (toggleBtn) {
545
+ toggleBtn.innerHTML = ptyInputMode ? '\\u2328 Input' : '\\u2328 View';
546
+ toggleBtn.classList.toggle('input-active', ptyInputMode);
547
+ toggleBtn.title = ptyInputMode
548
+ ? 'Input mode — keystrokes go to the live terminal (click for read-only)'
549
+ : 'Toggle input mode (type into the live terminal)';
550
+ }
551
+ if (ptyInputMode && term) {
552
+ term.focus(); // typing goes to xterm's textarea
553
+ } else {
554
+ // View mode: focus the container (tabindex) so PageUp/PageDown reach our
555
+ // key handler instead of scrolling the browser page.
556
+ const host = document.getElementById('pty-terminal');
557
+ if (host) host.focus();
558
+ }
559
+ }
560
+
561
+ function togglePtyInputMode() {
562
+ setPtyInputMode(!ptyInputMode);
563
+ }
564
+
497
565
  // Refresh: force a clean reconnect of the CURRENT session. The server replays
498
566
  // a freshly-serialized screen frame on subscribe, so this redraws from a clean
499
567
  // xterm state — a manual escape hatch if the live stream ever drifts.
@@ -505,8 +573,41 @@ function generateDashboardHtml(dashToken = '') {
505
573
  await openPtyViewer(agentId, sessionId);
506
574
  }
507
575
 
576
+ // Page Up / Page Down keys (Issue #201): the alt-screen mirror keeps no
577
+ // scrollback of its own, so a physical PageUp/PageDown key is forwarded to the
578
+ // live TUI — which holds the history and scrolls itself. In read-only view mode
579
+ // xterm's stdin is disabled, so without this those keys would just scroll the
580
+ // browser page; here we intercept ONLY these two keys (never printable input)
581
+ // and send them to the PTY, so the user can page through earlier output without
582
+ // enabling the interactive keyboard. In input mode xterm already forwards them
583
+ // via onData, so we skip to avoid double-send.
584
+ //
585
+ // The listener is attached to the document, NOT the viewer container: xterm renders
586
+ // its own focusable helpers inside #pty-terminal, and with disableStdin the
587
+ // container rarely holds focus reliably (a reconnect or stray click drops it),
588
+ // so a container-scoped keydown silently missed the keys. Gating on the viewer
589
+ // being open + view mode keeps it from hijacking PageUp elsewhere on the page,
590
+ // and we bail when focus is in a real text field so form paging still works.
591
+ // \\x1b[5~ = PageUp, \\x1b[6~ = PageDown.
592
+ function forwardPageKey(e) {
593
+ if (e.key !== 'PageUp' && e.key !== 'PageDown') return;
594
+ if (ptyInputMode) return; // input mode: xterm.onData already sends it
595
+ // Only while THIS viewer is actually open with a live socket.
596
+ const viewer = document.getElementById('pty-viewer');
597
+ if (!viewer || viewer.style.display === 'none') return;
598
+ if (!ptyWs || ptyWs.readyState !== WebSocket.OPEN) return;
599
+ // Don't steal paging from a genuine editable field (none in view mode today,
600
+ // but keep the guard so the document-level listener stays well-behaved).
601
+ const t = e.target;
602
+ if (t && (t.tagName === 'INPUT' || (t.tagName === 'TEXTAREA' && !t.disabled) || t.isContentEditable)) return;
603
+ e.preventDefault();
604
+ ptyWs.send(e.key === 'PageUp' ? '\\x1b[5~' : '\\x1b[6~');
605
+ }
606
+ document.addEventListener('keydown', forwardPageKey);
607
+
508
608
  document.getElementById('pty-close-btn').addEventListener('click', closePtyViewer);
509
609
  document.getElementById('pty-refresh-btn').addEventListener('click', function() { void refreshPtyViewer(); });
610
+ document.getElementById('pty-mode-toggle-btn').addEventListener('click', togglePtyInputMode);
510
611
 
511
612
  // Event delegation for Live buttons (avoids inline onclick + HTML injection)
512
613
  document.getElementById('sessions-tbody').addEventListener('click', function(e) {
@@ -1 +1 @@
1
- {"version":3,"file":"web-ui.js","sourceRoot":"","sources":["../../src/ui/web-ui.ts"],"names":[],"mappings":";;AAIA,sDAkxBC;AAtxBD;;;GAGG;AACH,SAAgB,qBAAqB,CAAC,SAAS,GAAG,EAAE;IAClD,MAAM,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC3E,OAAO;;;;;qCAK4B,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA0wBtC,CAAC;AACT,CAAC"}
1
+ {"version":3,"file":"web-ui.js","sourceRoot":"","sources":["../../src/ui/web-ui.ts"],"names":[],"mappings":";;AAIA,sDAu3BC;AA33BD;;;GAGG;AACH,SAAgB,qBAAqB,CAAC,SAAS,GAAG,EAAE;IAClD,MAAM,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC3E,OAAO;;;;;qCAK4B,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA+2BtC,CAAC;AACT,CAAC"}
@@ -6,7 +6,7 @@
6
6
  import * as fs from 'fs';
7
7
  import * as path from 'path';
8
8
  import { randomBytes } from 'crypto';
9
- import type { DiscordAccessConfig, DiscordMessageContext, DiscordAccess, DiscordGateResult } from './types';
9
+ import type { DiscordAccessConfig, DiscordMessageContext, DiscordAccess, DiscordGateResult, DiscordPending } from './types';
10
10
  export type { DiscordAccessConfig, DiscordAccess } from './types';
11
11
 
12
12
  export type AccessResult = { allowed: boolean; reason?: string };
@@ -15,10 +15,17 @@ export type AccessResult = { allowed: boolean; reason?: string };
15
15
  // File-based access (new — pairing flow)
16
16
  // ---------------------------------------------------------------------------
17
17
 
18
+ // Default for a brand-new agent (no access.json yet): closed base + pairing on,
19
+ // so the owner can DM the bot and capture their own id via a code. This matches
20
+ // the pre-split behavior where a missing file defaulted to dmPolicy:'pairing'.
18
21
  export function defaultAccess(): DiscordAccess {
19
22
  return {
20
- dmPolicy: 'pairing',
23
+ dmPolicy: 'allowlist',
24
+ pairing: true,
21
25
  allowFrom: [],
26
+ // Secure new-agent default: guilds closed + answer only when @mentioned.
27
+ groupPolicy: 'allowlist',
28
+ requireMention: true,
22
29
  guildAllowlist: [],
23
30
  channelAllowlist: [],
24
31
  roleAllowlist: [],
@@ -26,24 +33,83 @@ export function defaultAccess(): DiscordAccess {
26
33
  };
27
34
  }
28
35
 
36
+ /**
37
+ * Normalize a parsed access.json into the current split shape, migrating the
38
+ * legacy fused dmPolicy ('pairing' folded pairing in) to the split model.
39
+ * Mirrors telegram/pure.ts:migrateAccess.
40
+ *
41
+ * SECURITY: a legacy `allowlist` file was deliberately locked down — it must
42
+ * migrate to `pairing:false`, NOT true, or it would start minting codes for
43
+ * strangers. Here an absent `pairing` on an existing file means a pre-split
44
+ * file → pairing off. Only the brand-new/ENOENT path (defaultAccess) is on.
45
+ */
46
+ export function migrateAccess(parsed: {
47
+ dmPolicy?: string;
48
+ pairing?: boolean;
49
+ allowFrom?: string[];
50
+ groupPolicy?: string;
51
+ requireMention?: boolean;
52
+ guildAllowlist?: string[];
53
+ channelAllowlist?: string[];
54
+ roleAllowlist?: string[];
55
+ pending?: Record<string, DiscordPending>;
56
+ }): DiscordAccess {
57
+ const legacy = parsed.dmPolicy;
58
+ let dmPolicy: DiscordAccess['dmPolicy'];
59
+ let pairing: boolean;
60
+ if (legacy === 'pairing') {
61
+ dmPolicy = 'allowlist';
62
+ pairing = true;
63
+ } else {
64
+ dmPolicy = (legacy as DiscordAccess['dmPolicy']) ?? 'allowlist';
65
+ pairing = parsed.pairing ?? false;
66
+ }
67
+ const guildAllowlist = parsed.guildAllowlist ?? [];
68
+ // Guild tier migration must be behavior-preserving on upgrade. Today an empty
69
+ // guildAllowlist means "deliver to all guilds" — so derive 'open' when empty,
70
+ // 'allowlist' when non-empty; and requireMention defaults to false for
71
+ // existing files (they had no mention gate). New agents get the secure
72
+ // 'allowlist'+requireMention:true from defaultAccess() instead.
73
+ const groupPolicy = (parsed.groupPolicy as DiscordAccess['groupPolicy'])
74
+ ?? (guildAllowlist.length > 0 ? 'allowlist' : 'open');
75
+ const requireMention = parsed.requireMention ?? false;
76
+ return {
77
+ dmPolicy,
78
+ pairing,
79
+ allowFrom: parsed.allowFrom ?? [],
80
+ groupPolicy,
81
+ requireMention,
82
+ guildAllowlist,
83
+ channelAllowlist: parsed.channelAllowlist ?? [],
84
+ roleAllowlist: parsed.roleAllowlist ?? [],
85
+ pending: parsed.pending ?? {},
86
+ };
87
+ }
88
+
29
89
  export function loadAccess(stateDir: string): DiscordAccess {
30
90
  const accessFile = path.join(stateDir, 'access.json');
31
91
  try {
32
92
  const raw = fs.readFileSync(accessFile, 'utf8');
33
- const parsed = JSON.parse(raw) as Partial<DiscordAccess>;
34
- return {
35
- dmPolicy: parsed.dmPolicy ?? 'pairing',
36
- allowFrom: parsed.allowFrom ?? [],
37
- guildAllowlist: parsed.guildAllowlist ?? [],
38
- channelAllowlist: parsed.channelAllowlist ?? [],
39
- roleAllowlist: parsed.roleAllowlist ?? [],
40
- pending: parsed.pending ?? {},
41
- };
93
+ const parsed = JSON.parse(raw) as Partial<DiscordAccess> & { dmPolicy?: string };
94
+ return migrateAccess(parsed);
42
95
  } catch (err) {
43
96
  if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
44
- // Seed from env vars for backward compat
45
- return {
46
- dmPolicy: (process.env.DISCORD_DM_POLICY as DiscordAccess['dmPolicy']) ?? 'pairing',
97
+ // Brand-new agent (no access.json yet). If the deployment configured
98
+ // access via DISCORD_* env vars, honour that (legacy backward-compat,
99
+ // normalized through migrate). Otherwise fall back to defaultAccess() so
100
+ // the owner can DM the bot and self-pair — migrateAccess would yield
101
+ // pairing:false here (its "locked existing file" rule), which wrongly
102
+ // drops the owner's first DM on a fresh agent.
103
+ const hasEnvConfig = Boolean(
104
+ process.env.DISCORD_DM_POLICY ||
105
+ process.env.DISCORD_DM_ALLOWLIST ||
106
+ process.env.DISCORD_GUILD_ALLOWLIST ||
107
+ process.env.DISCORD_CHANNEL_ALLOWLIST ||
108
+ process.env.DISCORD_ROLE_ALLOWLIST,
109
+ );
110
+ if (!hasEnvConfig) return defaultAccess();
111
+ return migrateAccess({
112
+ dmPolicy: process.env.DISCORD_DM_POLICY,
47
113
  allowFrom: process.env.DISCORD_DM_ALLOWLIST
48
114
  ? process.env.DISCORD_DM_ALLOWLIST.split(',').filter(Boolean)
49
115
  : [],
@@ -57,7 +123,7 @@ export function loadAccess(stateDir: string): DiscordAccess {
57
123
  ? process.env.DISCORD_ROLE_ALLOWLIST.split(',').filter(Boolean)
58
124
  : [],
59
125
  pending: {},
60
- };
126
+ });
61
127
  }
62
128
  try {
63
129
  fs.renameSync(accessFile, `${accessFile}.corrupt-${Date.now()}`);
@@ -100,36 +166,77 @@ export function gate(
100
166
  const { isDM, userId, guildId, channelId } = context;
101
167
 
102
168
  if (!isDM) {
103
- if (access.guildAllowlist.length > 0 && guildId) {
104
- if (!access.guildAllowlist.includes(guildId)) return { action: 'drop' };
169
+ if (access.groupPolicy === 'disabled') return { action: 'drop' };
170
+
171
+ if (access.groupPolicy === 'allowlist' && guildId && !access.guildAllowlist.includes(guildId)) {
172
+ // Unknown guild. Pairing off ⇒ silent drop. On ⇒ mint a code keyed on the
173
+ // guild id and post it here; a member relays it to the admin (mirrors LINE).
174
+ if (!access.pairing) return { action: 'drop' };
175
+ for (const [code, p] of Object.entries(access.pending)) {
176
+ if (p.kind === 'guild' && p.guildId === guildId) {
177
+ if (p.replies >= 2) return { action: 'drop' };
178
+ p.replies++;
179
+ saveAccessFn(access);
180
+ return { action: 'pair', code, isResend: true, isGuild: true };
181
+ }
182
+ }
183
+ if (countPending(access, 'guild') >= 5) return { action: 'drop' };
184
+ const code = generateCode();
185
+ access.pending[code] = {
186
+ senderId: userId,
187
+ channelId,
188
+ guildId,
189
+ kind: 'guild',
190
+ createdAt: ts,
191
+ expiresAt: ts + 60 * 60 * 1000,
192
+ replies: 1,
193
+ };
194
+ saveAccessFn(access);
195
+ return { action: 'pair', code, isResend: false, isGuild: true };
105
196
  }
197
+
198
+ // Allowlisted (or open policy) → keep the channel filter, then mention gate.
106
199
  if (access.channelAllowlist.length > 0) {
107
200
  if (!access.channelAllowlist.includes(channelId)) return { action: 'drop' };
108
201
  }
202
+ if (access.requireMention !== false && !context.mentionsBot) return { action: 'drop' };
109
203
  return { action: 'deliver' };
110
204
  }
111
205
 
112
206
  // DM message
113
207
  if (access.dmPolicy === 'disabled') return { action: 'drop' };
114
208
 
209
+ if (access.dmPolicy === 'open') {
210
+ // Open base: anyone may DM; capture their id so future messages are known.
211
+ if (!access.allowFrom.includes(userId)) {
212
+ access.allowFrom.push(userId);
213
+ saveAccessFn(access);
214
+ }
215
+ return { action: 'deliver' };
216
+ }
217
+
115
218
  if (access.allowFrom.includes(userId)) return { action: 'deliver' };
116
- if (access.dmPolicy === 'allowlist') return { action: 'drop' };
219
+ // Base policy is 'allowlist' ('disabled'/'open' handled above). Pairing is the
220
+ // orthogonal toggle: off ⇒ pure allowlist (drop strangers, no code).
221
+ if (!access.pairing) return { action: 'drop' };
117
222
 
118
223
  // pairing mode
119
224
  for (const [code, p] of Object.entries(access.pending)) {
120
- if (p.senderId === userId) {
225
+ if ((p.kind ?? 'dm') === 'dm' && p.senderId === userId) {
121
226
  if (p.replies >= 2) return { action: 'drop' };
122
227
  p.replies++;
123
228
  saveAccessFn(access);
124
229
  return { action: 'pair', code, isResend: true };
125
230
  }
126
231
  }
127
- if (Object.keys(access.pending).length >= 3) return { action: 'drop' };
232
+ // Cap pending per-kind so guild knocks and DM knocks don't starve each other.
233
+ if (countPending(access, 'dm') >= 5) return { action: 'drop' };
128
234
 
129
235
  const code = generateCode();
130
236
  access.pending[code] = {
131
237
  senderId: userId,
132
238
  channelId,
239
+ kind: 'dm',
133
240
  createdAt: ts,
134
241
  expiresAt: ts + 60 * 60 * 1000,
135
242
  replies: 1,
@@ -138,6 +245,15 @@ export function gate(
138
245
  return { action: 'pair', code, isResend: false };
139
246
  }
140
247
 
248
+ /** Count pending entries of a given kind (absent kind ⇒ 'dm'). */
249
+ function countPending(access: DiscordAccess, kind: 'dm' | 'guild'): number {
250
+ let n = 0;
251
+ for (const p of Object.values(access.pending)) {
252
+ if ((p.kind ?? 'dm') === kind) n++;
253
+ }
254
+ return n;
255
+ }
256
+
141
257
  // ---------------------------------------------------------------------------
142
258
  // Env-var based access (backward compat)
143
259
  // ---------------------------------------------------------------------------
@@ -22,7 +22,9 @@ export async function createDiscordClient(token: string): Promise<any> {
22
22
  });
23
23
 
24
24
  await new Promise<void>((resolve, reject) => {
25
- client.once('ready', () => resolve());
25
+ // 'clientReady' is the renamed 'ready' event (discord.js v14 emits both;
26
+ // 'ready' is deprecated and becomes clientReady-only in v15).
27
+ client.once('clientReady', () => resolve());
26
28
  client.once('error', reject);
27
29
  client.login(token).catch(reject);
28
30
  });