elanous 0.2.11 → 0.2.12

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 (246) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/chunks/1147-466ec15afacc1a65.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/{1394-556de6d71bbdb0fb.js → 1394-ee3795b112907122.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/3869-3cd46526879f454c.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/7277-0b2fc7c16ba2d64b.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/7758-0b018a9e4fc56200.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/{8605-ea5bee9dfae512c7.js → 8605-0259994816604c49.js} +1 -1
  9. package/apps/pwa/out/_next/static/chunks/9920-5cfdc5e8fd209825.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/app/approvals/page-cb169620945d1952.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-6a061bd97f0b9398.js → page-cefe8169e36cf62a.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/app/board/{page-cb95a7432c9ee744.js → page-d2e35228bc497119.js} +1 -1
  13. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-b12b6d2f8d2d1aea.js → page-5c05b0da4bc1392c.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/app/bots/{page-b9efe868bfd36255.js → page-e0ccdb576812b523.js} +1 -1
  15. package/apps/pwa/out/_next/static/chunks/app/chat/{page-7c97dcdfe2e37a60.js → page-813c4a52c9d05ca8.js} +1 -1
  16. package/apps/pwa/out/_next/static/chunks/app/consult/page-4731f2c215358937.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/control/{page-5721537ca8a3598c.js → page-a022b257950f39e6.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-b654240a572e4c48.js → page-a2ffc717e04b8b0a.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/editor/{page-b9750abc897508c3.js → page-b59ee4e70fc18fcf.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/exec/page-a67e11ba962337b5.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/app/field/page-c172dabd796eb7a3.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/app/inside/page-1efd0cb9e0ed9d13.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/app/intake/{page-c8b1ccda6ba7ae98.js → page-d5dc7ff69a37b4cf.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/layout-e71c84189e8c683e.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/live/{page-2a9932ddb62d76bd.js → page-1d6a533ce5a6efcb.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/app/market/{page-9688f5b4f1ff950f.js → page-dfca9b7d2de0b135.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-793fa536b3a6ef37.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/app/morning/{page-35414e0a007b4082.js → page-6dd3bfbbde2a5c2c.js} +1 -1
  29. package/apps/pwa/out/_next/static/chunks/app/{not-found-e7a529e0507ac86c.js → not-found-490653e8d4cc8852.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/app/observatory/{page-e001823c35c9dd67.js → page-77447356baad75d2.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-29e961191f6057df.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-c822f55b884d9104.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fa4ccfc432424e52.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/app/outputs/{page-3a24072b35470157.js → page-6d604227e1664b71.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/page-9c7e8f744a765daf.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-0be8d4b2ced75ba0.js → page-4048543b4b7ad5e0.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-d37cb0d52f9b6bd5.js → page-4c3a3126abed551a.js} +1 -1
  38. package/apps/pwa/out/_next/static/chunks/app/sessions/{page-686da0df9bf23e5c.js → page-64b0aecb1ca75e75.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-4292d0149020e8a7.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/app/settings/{page-53f80edc6b8dc07c.js → page-db14d0be7f1c460c.js} +1 -1
  41. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-e5d8072cd7cfef73.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/setup/{page-239c17b911b84e72.js → page-9547b5cec7cf76bf.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/app/share/page-e6dcb83265530636.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/showroom/page-45248f08d6aecae5.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-1218753c82d585f6.js → page-2cddeb9e2aeedc55.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/term/{page-4b5935e67531efb5.js → page-e297866051595d81.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/app/today/{page-3285bc87caf1eaa5.js → page-baba2e047872fb0f.js} +1 -1
  48. package/apps/pwa/out/_next/static/chunks/app/trace/{page-ad553b0e06505ecd.js → page-e895da4ec9cac345.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/vault/page-4344faf20216233b.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-698bb29f11864b89.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/workflows/page-bd6a6dc1ddc273f2.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/workspace/page-2e9e2fb01c9da1c9.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-d62ceb13411ebba4.js +1 -0
  54. package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +1 -0
  55. package/apps/pwa/out/approvals/index.html +1 -1
  56. package/apps/pwa/out/approvals/index.txt +14 -14
  57. package/apps/pwa/out/autopilot/index.html +1 -1
  58. package/apps/pwa/out/autopilot/index.txt +14 -14
  59. package/apps/pwa/out/board/index.html +1 -1
  60. package/apps/pwa/out/board/index.txt +14 -14
  61. package/apps/pwa/out/botlab/index.html +1 -1
  62. package/apps/pwa/out/botlab/index.txt +14 -14
  63. package/apps/pwa/out/bots/index.html +1 -1
  64. package/apps/pwa/out/bots/index.txt +14 -14
  65. package/apps/pwa/out/chat/index.html +1 -1
  66. package/apps/pwa/out/chat/index.txt +14 -14
  67. package/apps/pwa/out/consult/index.html +1 -1
  68. package/apps/pwa/out/consult/index.txt +14 -14
  69. package/apps/pwa/out/control/index.html +1 -1
  70. package/apps/pwa/out/control/index.txt +14 -14
  71. package/apps/pwa/out/design-check/index.html +1 -1
  72. package/apps/pwa/out/design-check/index.txt +14 -14
  73. package/apps/pwa/out/editor/index.html +1 -1
  74. package/apps/pwa/out/editor/index.txt +14 -14
  75. package/apps/pwa/out/exec/index.html +1 -1
  76. package/apps/pwa/out/exec/index.txt +14 -14
  77. package/apps/pwa/out/field/index.html +1 -1
  78. package/apps/pwa/out/field/index.txt +14 -14
  79. package/apps/pwa/out/healthz +1 -1
  80. package/apps/pwa/out/index.html +1 -1
  81. package/apps/pwa/out/index.txt +14 -14
  82. package/apps/pwa/out/inside/index.html +1 -1
  83. package/apps/pwa/out/inside/index.txt +14 -14
  84. package/apps/pwa/out/intake/index.html +1 -1
  85. package/apps/pwa/out/intake/index.txt +14 -14
  86. package/apps/pwa/out/live/index.html +1 -1
  87. package/apps/pwa/out/live/index.txt +14 -14
  88. package/apps/pwa/out/market/index.html +1 -1
  89. package/apps/pwa/out/market/index.txt +14 -14
  90. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  91. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  92. package/apps/pwa/out/morning/index.html +1 -1
  93. package/apps/pwa/out/morning/index.txt +14 -14
  94. package/apps/pwa/out/observatory/index.html +1 -1
  95. package/apps/pwa/out/observatory/index.txt +14 -14
  96. package/apps/pwa/out/ops/checklist/index.html +1 -1
  97. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  98. package/apps/pwa/out/ops/release/index.html +1 -1
  99. package/apps/pwa/out/ops/release/index.txt +14 -14
  100. package/apps/pwa/out/ops/seats/index.html +1 -1
  101. package/apps/pwa/out/ops/seats/index.txt +14 -14
  102. package/apps/pwa/out/outputs/index.html +1 -1
  103. package/apps/pwa/out/outputs/index.txt +14 -14
  104. package/apps/pwa/out/reflection/index.html +1 -1
  105. package/apps/pwa/out/reflection/index.txt +15 -15
  106. package/apps/pwa/out/scheduler/index.html +1 -1
  107. package/apps/pwa/out/scheduler/index.txt +14 -14
  108. package/apps/pwa/out/sessions/index.html +1 -1
  109. package/apps/pwa/out/sessions/index.txt +14 -14
  110. package/apps/pwa/out/settings/devices/index.html +1 -1
  111. package/apps/pwa/out/settings/devices/index.txt +14 -14
  112. package/apps/pwa/out/settings/index.html +1 -1
  113. package/apps/pwa/out/settings/index.txt +14 -14
  114. package/apps/pwa/out/setup/done/index.html +1 -1
  115. package/apps/pwa/out/setup/done/index.txt +14 -14
  116. package/apps/pwa/out/setup/index.html +1 -1
  117. package/apps/pwa/out/setup/index.txt +14 -14
  118. package/apps/pwa/out/share/index.html +1 -1
  119. package/apps/pwa/out/share/index.txt +14 -14
  120. package/apps/pwa/out/showroom/index.html +1 -1
  121. package/apps/pwa/out/showroom/index.txt +14 -14
  122. package/apps/pwa/out/tasks/index.html +1 -1
  123. package/apps/pwa/out/tasks/index.txt +14 -14
  124. package/apps/pwa/out/term/index.html +1 -1
  125. package/apps/pwa/out/term/index.txt +14 -14
  126. package/apps/pwa/out/today/index.html +1 -1
  127. package/apps/pwa/out/today/index.txt +14 -14
  128. package/apps/pwa/out/trace/index.html +1 -1
  129. package/apps/pwa/out/trace/index.txt +14 -14
  130. package/apps/pwa/out/vault/index.html +1 -1
  131. package/apps/pwa/out/vault/index.txt +14 -14
  132. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  133. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  134. package/apps/pwa/out/workflows/index.html +1 -1
  135. package/apps/pwa/out/workflows/index.txt +14 -14
  136. package/apps/pwa/out/workspace/index.html +1 -1
  137. package/apps/pwa/out/workspace/index.txt +14 -14
  138. package/apps/pwa/out/worktrees/index.html +1 -1
  139. package/apps/pwa/out/worktrees/index.txt +14 -14
  140. package/graphs/context/nightly-condense.yaml +21 -0
  141. package/graphs/context/recipes.yaml +12 -0
  142. package/graphs/demo/inside-seed.yaml +20 -0
  143. package/graphs/demo/recipes.yaml +12 -0
  144. package/package.json +1 -1
  145. package/plugins/card-followup/examples/sample-card.png +0 -0
  146. package/scripts/lib/telegram-inject-args.ts +31 -0
  147. package/scripts/measure-pod-memory-by-goal.ts +130 -0
  148. package/scripts/release-loop/auto-approve-node.ts +1 -1
  149. package/scripts/release-loop/checklist-gate-node.ts +2 -1
  150. package/scripts/release-loop/gate-node.ts +8 -2
  151. package/scripts/release-loop/ops-upgrade-node.ts +47 -6
  152. package/scripts/release-loop/publish-node.ts +4 -1
  153. package/scripts/release-loop/version-node.ts +13 -3
  154. package/scripts/telegram-inject.ts +14 -6
  155. package/scripts/test-diet/remeasure-list.ts +112 -0
  156. package/scripts/ux/demo-rehearsal.ts +17 -13
  157. package/scripts/ux/demo-seed.ts +108 -0
  158. package/scripts/ux/lib/rehearsal-verdict.ts +12 -5
  159. package/src/agent-mission/driver.ts +138 -16
  160. package/src/agent-mission/pty-decision.ts +1 -1
  161. package/src/autopilot/pty-control-loop.ts +4 -1
  162. package/src/chat/index.ts +1 -0
  163. package/src/cli/persona-cli.ts +17 -14
  164. package/src/cli/pod-cli.ts +17 -0
  165. package/src/cli/pod-memory-advice.ts +35 -0
  166. package/src/cli/release-cli.ts +46 -6
  167. package/src/context-bus/context-now-surfaces.ts +17 -0
  168. package/src/context-bus/context-now.ts +2 -2
  169. package/src/context-bus/coord-events.ts +2 -2
  170. package/src/context-bus/nightly-condense.ts +153 -0
  171. package/src/context-bus/session-events.ts +35 -0
  172. package/src/dashboard/dashboard-status-lines.ts +2 -0
  173. package/src/dashboard/index.ts +8 -0
  174. package/src/dashboard/slash-runtime/dashboard-handlers.ts +24 -0
  175. package/src/domains/outbound-alert.ts +99 -31
  176. package/src/domains/outbound-log-probe.ts +8 -0
  177. package/src/execution-loop/launch-gate.ts +17 -8
  178. package/src/field-note/telegram.ts +142 -0
  179. package/src/graph-runner/graph-grow-llm.ts +23 -13
  180. package/src/graph-runner/graph-grow.ts +5 -2
  181. package/src/graph-runner/runner.ts +12 -8
  182. package/src/harness/harness-cli-command.ts +138 -16
  183. package/src/hooks/consult-intake.ts +153 -0
  184. package/src/hooks/receiver.ts +78 -1
  185. package/src/index.ts +33 -9
  186. package/src/intake-plane/discord-seat-work.ts +28 -2
  187. package/src/intake-plane/wish-card.ts +33 -0
  188. package/src/llm/difficulty.ts +1 -1
  189. package/src/maturity/feature-maturity.ts +2 -1
  190. package/src/nexus/api/http-server.ts +31 -8
  191. package/src/nexus/api/outbound-report.ts +40 -7
  192. package/src/nexus/api/personas.ts +95 -53
  193. package/src/nexus/api/task-cards-api.ts +19 -0
  194. package/src/nexus/api/workflow-history.ts +88 -0
  195. package/src/nexus/api/workflows.ts +52 -0
  196. package/src/nexus/outbound/delivery-ledger.ts +17 -8
  197. package/src/nexus/outbound/router.ts +8 -3
  198. package/src/persona/presets.ts +8 -1
  199. package/src/plugins/maker/market-bundle.ts +2 -1
  200. package/src/release-loop/checklist.ts +15 -3
  201. package/src/release-loop/feature-store.ts +24 -10
  202. package/src/release-loop/landed-but-yellow.ts +48 -0
  203. package/src/seat-loop/seat-loop.ts +168 -7
  204. package/src/self-dev/draft-sweep.ts +44 -10
  205. package/src/self-dev/draft-triage-rules.ts +54 -2
  206. package/src/self-implement/budget-gate.ts +29 -12
  207. package/src/self-implement/gate-candidate-ranking.ts +60 -0
  208. package/src/self-implement/gate-scope.ts +9 -8
  209. package/src/self-implement/headless-elanous-driver.ts +7 -2
  210. package/src/self-implement/orchestrator.ts +15 -0
  211. package/src/self-implement/test-seams.ts +1 -0
  212. package/src/task-orchestrator/surfaces/pod-command-job.ts +14 -10
  213. package/src/task-orchestrator/surfaces/self-implement-pod.ts +8 -0
  214. package/src/telegram-commands.ts +21 -0
  215. package/src/telegram-project-command.ts +78 -4
  216. package/src/telegram.ts +32 -5
  217. package/src/user-config.ts +8 -4
  218. package/src/version/packed-revision.json +1 -1
  219. package/apps/pwa/out/_next/static/chunks/2310-7a59f043f6db207e.js +0 -1
  220. package/apps/pwa/out/_next/static/chunks/2548-6e316138d347cefe.js +0 -1
  221. package/apps/pwa/out/_next/static/chunks/3869-a39d1d9921e10d43.js +0 -1
  222. package/apps/pwa/out/_next/static/chunks/507-5acd98bd6dab2d8a.js +0 -1
  223. package/apps/pwa/out/_next/static/chunks/5445-745eb795c5963d79.js +0 -1
  224. package/apps/pwa/out/_next/static/chunks/app/approvals/page-1dcdc58fc2de2454.js +0 -1
  225. package/apps/pwa/out/_next/static/chunks/app/consult/page-8fd36984f549270a.js +0 -1
  226. package/apps/pwa/out/_next/static/chunks/app/exec/page-4cd1de371f8ad048.js +0 -1
  227. package/apps/pwa/out/_next/static/chunks/app/field/page-d99f89657828421c.js +0 -1
  228. package/apps/pwa/out/_next/static/chunks/app/inside/page-50cb2a0eb15e6c83.js +0 -1
  229. package/apps/pwa/out/_next/static/chunks/app/layout-d6b1d156fb95e74b.js +0 -1
  230. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-6b15dd6456c5df09.js +0 -1
  231. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-371423d1a573d64b.js +0 -1
  232. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-4b764b125339ac17.js +0 -1
  233. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cdf8e4b01067bcf1.js +0 -1
  234. package/apps/pwa/out/_next/static/chunks/app/page-94c82eb0ae5abb32.js +0 -1
  235. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-4dc614f3ce043e63.js +0 -1
  236. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-d02c782fc792e2af.js +0 -1
  237. package/apps/pwa/out/_next/static/chunks/app/share/page-dc6a79fb003f746c.js +0 -1
  238. package/apps/pwa/out/_next/static/chunks/app/showroom/page-0fffb84cafdc9da6.js +0 -1
  239. package/apps/pwa/out/_next/static/chunks/app/vault/page-fca096e3e860306f.js +0 -1
  240. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-9ebfa78f0d9885c3.js +0 -1
  241. package/apps/pwa/out/_next/static/chunks/app/workflows/page-3dc5f21f7e39622d.js +0 -1
  242. package/apps/pwa/out/_next/static/chunks/app/workspace/page-c6da2b468454532d.js +0 -1
  243. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-93bc70e36f595414.js +0 -1
  244. package/apps/pwa/out/_next/static/css/543f56bcb1f79fca.css +0 -1
  245. /package/apps/pwa/out/_next/static/{Y4zKzL62sakG5momz3SoQ → -6XrAoD3eU7NyMHAE4xPS}/_buildManifest.js +0 -0
  246. /package/apps/pwa/out/_next/static/{Y4zKzL62sakG5momz3SoQ → -6XrAoD3eU7NyMHAE4xPS}/_ssgManifest.js +0 -0
@@ -1,7 +1,10 @@
1
1
  import { debug } from './debug/log.js';
2
2
  import { getProject, listProjects, type Project } from './project/project-store.js';
3
3
  import { findSessionByTelegramChat, updateSessionMeta } from './session/index.js';
4
+ import { telegramDecisionOwner } from './decisions/telegram-decision-cards.js';
5
+ import type { TelegramBot } from './telegram.js';
4
6
  import type { TgSlashCommand } from './telegram-commands.js';
7
+ import type { UserConfig } from './user-config.js';
5
8
 
6
9
  export interface TelegramProjectCommandDeps {
7
10
  list: () => Project[];
@@ -19,7 +22,7 @@ export function projectCommand(deps: TelegramProjectCommandDeps = {
19
22
  return {
20
23
  name: 'project',
21
24
  description: '이 대화의 프로젝트 보기·옮기기',
22
- handler: async (args, ctx) => {
25
+ handler: async (args, ctx, ctxOptions) => {
23
26
  const session = deps.findSession(ctx.chatId, ctx.threadId, ctx.botId);
24
27
  if (!session) {
25
28
  debug.log('telegram.project', 'rejected', { projects: [], reason: 'no-session' });
@@ -28,13 +31,32 @@ export function projectCommand(deps: TelegramProjectCommandDeps = {
28
31
 
29
32
  if (args.length === 0) {
30
33
  const current = session.projectId ? deps.find(session.projectId) : null;
31
- const projects = deps.list().sort((a, b) => a.name.localeCompare(b.name)).slice(0, 20);
32
- debug.log('telegram.project', 'listed', { projects: projects.map(p => ({ id: p.id, name: p.name })) });
34
+ const projects = deps.list().sort((a, b) => a.name.localeCompare(b.name));
35
+ debug.log('telegram.project', 'listed', { projects: projects.slice(0, 20).map(p => ({ id: p.id, name: p.name })) });
36
+ if (ctx.isDm && ctx.chatId === ctx.userId
37
+ && String(ctx.userId) === telegramDecisionOwner(ctxOptions.userConfig) && ctxOptions.sendButtons) {
38
+ const buttons = projects.filter(p => {
39
+ const bytes = Buffer.byteLength(`prj:${p.id}`, 'utf8');
40
+ if (bytes <= 64) return true;
41
+ debug.log('telegram.project', 'button-rejected', { reason: 'callback-data-too-long', projectId: p.id, bytes });
42
+ return false;
43
+ }).slice(0, 8).map(p => ({ text: p.name, data: `prj:${p.id}` }));
44
+ buttons.push({ text: '받은 대화로', data: 'prj:-' });
45
+ const rows: Array<Array<{ text: string; data: string }>> = [];
46
+ for (let i = 0; i < buttons.length; i += 2) rows.push(buttons.slice(i, i + 2));
47
+ try {
48
+ await ctxOptions.sendButtons('프로젝트를 선택하세요', rows);
49
+ } catch (err) {
50
+ debug.log('telegram.project', 'button-rejected', {
51
+ reason: 'send-failed', error: err instanceof Error ? err.message : String(err),
52
+ });
53
+ }
54
+ }
33
55
  return [
34
56
  `지금 대화의 프로젝트: ${current?.name ?? '받은 대화(프로젝트 없음)'}`,
35
57
  '',
36
58
  '프로젝트 목록:',
37
- ...projects.map(p => `• ${p.name}`),
59
+ ...projects.slice(0, 20).map(p => `• ${p.name}`),
38
60
  '',
39
61
  '옮기려면 /project <이름>',
40
62
  ].join('\n');
@@ -69,3 +91,55 @@ export function projectCommand(deps: TelegramProjectCommandDeps = {
69
91
  },
70
92
  };
71
93
  }
94
+
95
+ export function attachTelegramProjectButtons(
96
+ bot: Pick<TelegramBot, 'botId' | 'onCallbackQuery' | 'sendMessage' | 'answerCallbackQuery'>,
97
+ cfg: UserConfig,
98
+ deps: TelegramProjectCommandDeps = {
99
+ list: listProjects,
100
+ find: getProject,
101
+ update: updateSessionMeta,
102
+ findSession: findSessionByTelegramChat,
103
+ },
104
+ ): () => void {
105
+ const owner = telegramDecisionOwner(cfg);
106
+ return bot.onCallbackQuery(async q => {
107
+ if (!q.data.startsWith('prj:')) return;
108
+ if (!owner || String(q.userId) !== owner) {
109
+ debug.log('telegram.project', 'button-rejected', { reason: 'not-owner', userId: q.userId, chatId: q.chatId });
110
+ await bot.answerCallbackQuery(q.id, { text: '권한이 없습니다' });
111
+ return;
112
+ }
113
+ if (q.chatId === undefined || q.chatId !== q.userId || q.threadId !== undefined) {
114
+ debug.log('telegram.project', 'button-rejected', { reason: 'not-private-chat', userId: q.userId, chatId: q.chatId });
115
+ await bot.answerCallbackQuery(q.id, { text: '개인 대화에서만 고를 수 있습니다' });
116
+ return;
117
+ }
118
+ const chatId = q.chatId;
119
+ const session = deps.findSession(chatId, q.threadId, bot.botId);
120
+ if (!session) {
121
+ debug.log('telegram.project', 'button-rejected', { reason: 'no-session', chatId });
122
+ await bot.answerCallbackQuery(q.id);
123
+ await bot.sendMessage(chatId, '아직 이 채팅에 대화가 없습니다 — 한 마디 보낸 뒤 다시');
124
+ return;
125
+ }
126
+ if (q.data === 'prj:-') {
127
+ deps.update(session.id, m => { delete m.projectId; });
128
+ debug.log('telegram.project', 'button-cleared', { chatId, sessionId: session.id });
129
+ await bot.answerCallbackQuery(q.id);
130
+ await bot.sendMessage(chatId, '받은 대화로 옮겼습니다');
131
+ return;
132
+ }
133
+ const project = deps.find(q.data.slice(4));
134
+ if (!project) {
135
+ debug.log('telegram.project', 'button-rejected', { reason: 'not-found', chatId, projectId: q.data.slice(4) });
136
+ await bot.answerCallbackQuery(q.id);
137
+ await bot.sendMessage(chatId, '그 이름의 프로젝트가 없습니다 — /project 로 목록 보기');
138
+ return;
139
+ }
140
+ deps.update(session.id, m => { m.projectId = project.id; });
141
+ debug.log('telegram.project', 'button-moved', { chatId, sessionId: session.id, projectId: project.id });
142
+ await bot.answerCallbackQuery(q.id);
143
+ await bot.sendMessage(chatId, `이 대화를 ${project.name} 프로젝트로 옮겼습니다`);
144
+ });
145
+ }
package/src/telegram.ts CHANGED
@@ -73,7 +73,10 @@ import { effectiveInstanceRoot } from './instance/resolve.js';
73
73
  import { getElanousConfigDir } from './elanous-config-dir.js';
74
74
  import { defaultFieldEvent, FieldUploadError, parseFieldCaption, resolveFieldMime, saveFieldMedia } from './field/field-media.js';
75
75
  import { scheduleFieldReel, type FieldReelOptions } from './field/field-reel.js';
76
+ import { handleTelegramFieldNote } from './field-note/telegram.js';
77
+ import type { TranscriptionEngine, FieldAudio } from './field-note/transcribe.js';
76
78
  import { maybeHandleCardPhoto, type CardPhotoDeps } from './telegram-card-followup.js';
79
+ import { attachTelegramProjectButtons } from './telegram-project-command.js';
77
80
 
78
81
  export type TgAttachmentKind = 'photo' | 'voice' | 'audio' | 'document';
79
82
 
@@ -101,6 +104,8 @@ export interface TgIncoming {
101
104
  text: string;
102
105
  messageId: number;
103
106
  threadId?: number;
107
+ /** Telegram message timestamp, not poll time. */
108
+ receivedAt?: string;
104
109
  isDm: boolean;
105
110
  isGroup: boolean;
106
111
  /** Zero or more attached media — populated by parseUpdate from
@@ -219,6 +224,9 @@ export interface TelegramBotOpts {
219
224
  fieldRootDir?: () => string;
220
225
  /** 시험에서는 실제 렌더 대신 가짜 실행기를 주입한다. */
221
226
  fieldReel?: FieldReelOptions;
227
+ /** NOTE3a engine and audio decoder are injected; no transcription provider is assumed. */
228
+ fieldNoteEngine?: TranscriptionEngine;
229
+ fieldNoteDecodeAudio?: (file: string, recordedAt: string) => FieldAudio;
222
230
  /** Test seam for the business-card graph and its private run root. */
223
231
  cardFollowupDeps?: Pick<CardPhotoDeps, 'runGraph' | 'rootDir' | 'ocrText'>;
224
232
  }
@@ -278,7 +286,7 @@ export function classifyTelegramText(
278
286
 
279
287
  export class TelegramBot {
280
288
  private readonly token: string;
281
- private readonly botId: string;
289
+ readonly botId: string;
282
290
  private readonly allowedUsers: Set<number>;
283
291
  private readonly onMessage: TelegramBotOpts['onMessage'];
284
292
  private readonly fetchImpl: typeof fetch;
@@ -338,6 +346,8 @@ export class TelegramBot {
338
346
  private readonly fieldDefaultEventOpt?: string;
339
347
  private readonly fieldRootDir: () => string;
340
348
  private readonly fieldReel?: FieldReelOptions;
349
+ private readonly fieldNoteEngine?: TranscriptionEngine;
350
+ private readonly fieldNoteDecodeAudio?: (file: string, recordedAt: string) => FieldAudio;
341
351
  private readonly cardFollowupDeps: Pick<CardPhotoDeps, 'runGraph' | 'rootDir' | 'ocrText'>;
342
352
  private readonly seatWorkDeps: TelegramSeatWorkDeps;
343
353
  private seatAskTimer: ReturnType<typeof setInterval> | null = null;
@@ -375,6 +385,8 @@ export class TelegramBot {
375
385
  this.fieldDefaultEventOpt = opts.fieldDefaultEvent;
376
386
  this.fieldRootDir = opts.fieldRootDir ?? getElanousConfigDir;
377
387
  this.fieldReel = opts.fieldReel;
388
+ this.fieldNoteEngine = opts.fieldNoteEngine;
389
+ this.fieldNoteDecodeAudio = opts.fieldNoteDecodeAudio;
378
390
  this.cardFollowupDeps = opts.cardFollowupDeps ?? {};
379
391
  this.seatWorkDeps = { ...opts.seatWorkDeps, askDeps: { ...opts.seatWorkDeps?.askDeps,
380
392
  channel: 'telegram', botId: this.botId,
@@ -1214,6 +1226,7 @@ export class TelegramBot {
1214
1226
  userId: cq.from.id,
1215
1227
  userName: cq.from.username ?? cq.from.first_name,
1216
1228
  chatId: cq.message?.chat.id,
1229
+ threadId: cq.message?.message_thread_id,
1217
1230
  messageId: cq.message?.message_id,
1218
1231
  data: cq.data ?? '',
1219
1232
  };
@@ -1528,6 +1541,13 @@ export class TelegramBot {
1528
1541
  }
1529
1542
  // 현장 업로드 — `#현장` 캡션(또는 태그된 앨범의 나머지)의 사진·영상은 LLM 대신 현장 폴더로.
1530
1543
  if (await this.tryHandleFieldUpload(ctx)) return;
1544
+ if (await handleTelegramFieldNote(ctx, {
1545
+ rootDir: this.fieldRootDir(),
1546
+ download: (fileId) => this.downloadFile(fileId),
1547
+ reply: async (incoming, text) => { await this.sendMessage(incoming.chatId, text, { replyTo: incoming.messageId, threadId: incoming.threadId }); },
1548
+ engine: this.fieldNoteEngine, decodeAudio: this.fieldNoteDecodeAudio,
1549
+ now: () => new Date(this.nowImpl()),
1550
+ })) return;
1531
1551
  if (await maybeHandleCardPhoto(ctx, {
1532
1552
  isOwner: (userId) => this.isOwnerAllowed(userId),
1533
1553
  isFieldAlbum: (id) => this.fieldGroups.has(id),
@@ -1827,6 +1847,7 @@ export class TelegramBot {
1827
1847
  const reply = await slashParse.cmd.handler(slashParse.args, ctx, {
1828
1848
  userConfig: this.slashContext.userConfig,
1829
1849
  allCommands: this.slashCommands,
1850
+ sendButtons: async (text, buttons) => { await this.sendInlineKeyboard(ctx.chatId, text, buttons, { replyTo: ctx.messageId, threadId: ctx.threadId }); },
1830
1851
  downloadAttachments: () => this.downloadCtxAttachments(ctx),
1831
1852
  daemonBridge: this.slashContext.daemonBridge,
1832
1853
  hitlConfirmChannel: this.hitlConfirmChannelForChat(ctx.chatId, ctx.threadId),
@@ -2011,6 +2032,7 @@ export class TelegramBot {
2011
2032
  return await slashParse.cmd.handler(slashParse.args, ctx, {
2012
2033
  userConfig: this.slashContext!.userConfig,
2013
2034
  allCommands: this.slashCommands,
2035
+ sendButtons: async (text, buttons) => { await this.sendInlineKeyboard(ctx.chatId, text, buttons, { replyTo: ctx.messageId, threadId: ctx.threadId }); },
2014
2036
  streamer,
2015
2037
  downloadAttachments: () => this.downloadCtxAttachments(ctx),
2016
2038
  daemonBridge: this.slashContext!.daemonBridge,
@@ -2030,6 +2052,7 @@ export class TelegramBot {
2030
2052
  return await runAcpViaSlash(activeBackend, [ctx.text], ctx, {
2031
2053
  userConfig: this.slashContext!.userConfig,
2032
2054
  allCommands: this.slashCommands,
2055
+ sendButtons: async (text, buttons) => { await this.sendInlineKeyboard(ctx.chatId, text, buttons, { replyTo: ctx.messageId, threadId: ctx.threadId }); },
2033
2056
  streamer,
2034
2057
  downloadAttachments: () => this.downloadCtxAttachments(ctx),
2035
2058
  daemonBridge: this.slashContext!.daemonBridge,
@@ -2270,6 +2293,7 @@ interface RawUpdate {
2270
2293
  update_id: number;
2271
2294
  message?: {
2272
2295
  message_id: number;
2296
+ date?: number;
2273
2297
  from?: { id: number; first_name?: string; username?: string };
2274
2298
  chat: { id: number; type: 'private' | 'group' | 'supergroup' | 'channel' };
2275
2299
  text?: string;
@@ -2286,7 +2310,7 @@ interface RawUpdate {
2286
2310
  callback_query?: {
2287
2311
  id: string;
2288
2312
  from: { id: number; first_name?: string; username?: string };
2289
- message?: { message_id: number; chat: { id: number } };
2313
+ message?: { message_id: number; message_thread_id?: number; chat: { id: number } };
2290
2314
  data?: string;
2291
2315
  chat_instance?: string;
2292
2316
  };
@@ -2305,6 +2329,7 @@ export interface TgCallbackQuery {
2305
2329
  userId: number;
2306
2330
  userName?: string;
2307
2331
  chatId?: number;
2332
+ threadId?: number;
2308
2333
  messageId?: number;
2309
2334
  data: string;
2310
2335
  }
@@ -2397,6 +2422,7 @@ export function parseUpdate(u: RawUpdate): TgIncoming | null {
2397
2422
  text: (typeof m.text === 'string' ? m.text : (m.caption ?? '')),
2398
2423
  messageId: m.message_id,
2399
2424
  threadId: m.message_thread_id,
2425
+ ...(typeof m.date === 'number' ? { receivedAt: new Date(m.date * 1000).toISOString() } : {}),
2400
2426
  isDm,
2401
2427
  isGroup: !isDm,
2402
2428
  attachments,
@@ -2912,9 +2938,10 @@ export function botFromConfig(opts: BotFromConfigOpts): TelegramBot {
2912
2938
  botRef.ensureSurfaceHitl();
2913
2939
  // DEC-TG(대표 10-02) — decision cards on the MAIN bot only (other channel pollers carry other tokens). The ticker
2914
2940
  // itself sends only while this bot polls, so a send-only nexus bot never duplicates the standalone poller's cards.
2915
- if (opts.userConfig.raw?.decisions === undefined || (opts.userConfig.raw.decisions as { cards?: unknown }).cards !== false) {
2916
- const mainToken = resolveChannelBotToken('telegram', opts.userConfig)?.token;
2917
- if (mainToken && botToken === mainToken) {
2941
+ const mainToken = resolveChannelBotToken('telegram', opts.userConfig)?.token;
2942
+ if (mainToken && botToken === mainToken) {
2943
+ attachTelegramProjectButtons(botRef, opts.userConfig);
2944
+ if (opts.userConfig.raw?.decisions === undefined || (opts.userConfig.raw.decisions as { cards?: unknown }).cards !== false) {
2918
2945
  const bot = botRef;
2919
2946
  void import('./decisions/telegram-decision-cards.js')
2920
2947
  .then(({ attachTelegramDecisionCards }) => { attachTelegramDecisionCards(bot, opts.userConfig); })
@@ -3240,7 +3240,7 @@ export interface AutoReviewConfig {
3240
3240
  };
3241
3241
  }
3242
3242
 
3243
- export type ModelRole = 'implement' | 'review' | 'research' | 'planning' | 'audit' | 'classify';
3243
+ export type ModelRole = 'implement' | 'review' | 'research' | 'planning' | 'audit' | 'classify' | 'graph-grow';
3244
3244
 
3245
3245
  export interface RoleModelConfig {
3246
3246
  implement?: string;
@@ -3249,6 +3249,7 @@ export interface RoleModelConfig {
3249
3249
  planning?: string;
3250
3250
  audit?: string;
3251
3251
  classify?: string;
3252
+ 'graph-grow'?: string;
3252
3253
  }
3253
3254
 
3254
3255
  /** Sparse provider-agnostic model tier preferences by execution role. */
@@ -3315,6 +3316,7 @@ export const ROLE_MODEL_DEFAULTS: Record<ModelRole, { environment: string; tier:
3315
3316
  planning: { environment: 'ELANOUS_SKILL_PLAN_MODEL', tier: 'best' },
3316
3317
  audit: { environment: 'ELANOUS_MEMORY_JUDGE_MODEL', tier: 'better' },
3317
3318
  classify: { environment: 'ELANOUS_ACTION_CLASSIFIER_MODEL', tier: 'budget' },
3319
+ 'graph-grow': { environment: 'ELANOUS_GRAPH_GROW_MODEL', tier: 'better' },
3318
3320
  };
3319
3321
 
3320
3322
  /** Resolve `auto` through the same runtime provider selector that dispatches LLM calls.
@@ -3530,7 +3532,7 @@ export interface UserConfig {
3530
3532
  loops?: { steward?: { mode?: StewardLoopMode; linearTeam?: string; roles?: Record<string, { maxConcurrent?: number }>; budget?: number; tracks?: Record<string, string>; alertAfterFailures?: number; launch?: 'off' | 'shadow' | 'live'; maxParallel?: number; podPool?: string }; seat?: SeatLoopConfig; persona?: PersonaLoopConfig };
3531
3533
  /** 🔐 Pod 의 Grok API 키 과금은 명시 opt-in 만. 생략·잘못된 값은 false.
3532
3534
  * budgetGate 와 같은 `harness` 객체다 — 중복 선언이면 뒤 타입이 앞을 지운다. */
3533
- harness?: { pod?: { grokApiKeyOptIn: boolean }; budgetGate?: HarnessBudgetGateConfig; defaultRepo?: string; substrate?: 'local' | 'pod'; podPool?: string; worktreeAddTimeoutSec?: number };
3535
+ harness?: { pod?: { grokApiKeyOptIn: boolean }; budgetGate?: HarnessBudgetGateConfig; difficultyPlacement?: boolean; defaultRepo?: string; substrate?: 'local' | 'pod'; podPool?: string; worktreeAddTimeoutSec?: number };
3534
3536
  /** ☸️ Pod 실행 칸 — `pool` = 기존 기본 풀(`컨텍스트[@ssh호스트][:상한][#k3d-레지스트리:포트]` 쉼표 · 앞이 우선). harness 실행은 인자·ELANOUS_POD_POOL·harness.podPool 다음으로 읽는다. */
3535
3537
  pod?: { pool?: string; /** 호스트 Git 미러 디렉터리 — Pod Job 에 읽기 전용으로 마운트한다. */ hostMirror?: string; /** 원격 그라운딩 엔드포인트(호스트 nexus · tailnet 주소) — Pod 가 토큰으로 «질의→인용»만 묻는다(P13). */ groundingUrl?: string; /** 읽기 전용 Pod lease 권장에 쓰는 계정별 동시 수. */ lease?: { perAccount?: number } };
3536
3538
  skillRouter: SkillRouterConfig;
@@ -3700,7 +3702,7 @@ export function parseRoleLlmEntry(raw: unknown): { ok: true; spec: RoleLlmSpec }
3700
3702
  return { ok: true, spec };
3701
3703
  }
3702
3704
 
3703
- export const MODEL_ROLES: readonly ModelRole[] = ['implement', 'review', 'research', 'planning', 'audit', 'classify'];
3705
+ export const MODEL_ROLES: readonly ModelRole[] = ['implement', 'review', 'research', 'planning', 'audit', 'classify', 'graph-grow'];
3704
3706
 
3705
3707
  export function isModelRole(v: unknown): v is ModelRole {
3706
3708
  return typeof v === 'string' && (MODEL_ROLES as readonly string[]).includes(v);
@@ -3928,7 +3930,7 @@ function defaultConfig(): UserConfig {
3928
3930
  nativeStructure: { ...TOOLS_DEFAULTS.nativeStructure },
3929
3931
  selfImplement: { ...TOOLS_DEFAULTS.selfImplement },
3930
3932
  },
3931
- harness: { budgetGate: { ...DEFAULT_BUDGET_GATE } },
3933
+ harness: { budgetGate: { ...DEFAULT_BUDGET_GATE }, difficultyPlacement: false },
3932
3934
  grounding: { sources: [] },
3933
3935
  raw: {},
3934
3936
  };
@@ -4539,6 +4541,7 @@ export function buildUserConfig(path: string = defaultPath()): UserConfig {
4539
4541
  },
4540
4542
  harness: {
4541
4543
  pod: { grokApiKeyOptIn: harnessPod.grokApiKeyOptIn === true },
4544
+ difficultyPlacement: harness.difficultyPlacement === true,
4542
4545
  ...(harness.substrate === 'local' || harness.substrate === 'pod' ? { substrate: harness.substrate } : {}),
4543
4546
  ...(typeof harness.podPool === 'string' && harness.podPool.trim() ? { podPool: harness.podPool.trim() } : {}),
4544
4547
  ...(typeof harness.worktreeAddTimeoutSec === 'number' && Number.isSafeInteger(harness.worktreeAddTimeoutSec)
@@ -5783,6 +5786,7 @@ export function saveUserConfig(
5783
5786
  ...rawHarness,
5784
5787
  pod: cfg.harness?.pod ?? rawHarness.pod,
5785
5788
  budgetGate: cfg.harness?.budgetGate ?? rawHarness.budgetGate,
5789
+ difficultyPlacement: cfg.harness?.difficultyPlacement ?? rawHarness.difficultyPlacement,
5786
5790
  defaultRepo: cfg.harness?.defaultRepo ?? rawHarness.defaultRepo,
5787
5791
  substrate: cfg.harness?.substrate,
5788
5792
  podPool: cfg.harness?.podPool,
@@ -1 +1 @@
1
- {"commit":"e61a9846510a7f6ac0a0f223d8e4c8b384685df8"}
1
+ {"commit":"27045f4612b148543fb0f91b82d37592d1d6c9f2"}