@gajae-code/coding-agent 0.8.2 → 0.9.1

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 (214) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/types/append-only-mode.d.ts +31 -0
  3. package/dist/types/capability/rule.d.ts +8 -0
  4. package/dist/types/cli/fixture-report.d.ts +20 -0
  5. package/dist/types/cli/fixture-report.test.d.ts +1 -0
  6. package/dist/types/cli/nofile-limit.d.ts +11 -0
  7. package/dist/types/cli/update-cli.d.ts +22 -0
  8. package/dist/types/config/settings-schema.d.ts +119 -1
  9. package/dist/types/discovery/index.d.ts +1 -0
  10. package/dist/types/export/html/template.generated.d.ts +1 -1
  11. package/dist/types/export/ttsr.d.ts +14 -3
  12. package/dist/types/gjc-runtime/deep-interview-state.d.ts +5 -2
  13. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +7 -0
  14. package/dist/types/gjc-runtime/state-writer.d.ts +3 -0
  15. package/dist/types/gjc-runtime/team-runtime.d.ts +26 -0
  16. package/dist/types/gjc-runtime/tmux-common.d.ts +1 -0
  17. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +42 -0
  18. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +119 -13
  19. package/dist/types/goals/runtime.d.ts +1 -0
  20. package/dist/types/hooks/native-skill-hook.d.ts +12 -0
  21. package/dist/types/modes/components/assistant-message.d.ts +5 -1
  22. package/dist/types/modes/components/status-line.d.ts +9 -0
  23. package/dist/types/modes/components/thinking-selector.d.ts +2 -2
  24. package/dist/types/modes/controllers/command-controller.d.ts +1 -0
  25. package/dist/types/modes/controllers/event-controller.d.ts +5 -0
  26. package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
  27. package/dist/types/modes/interactive-mode.d.ts +4 -0
  28. package/dist/types/modes/rpc/rpc-mode.d.ts +1 -3
  29. package/dist/types/modes/rpc/rpc-socket-security.d.ts +3 -0
  30. package/dist/types/modes/rpc/rpc-types.d.ts +14 -0
  31. package/dist/types/modes/shared/agent-wire/event-contract.d.ts +19 -0
  32. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +23 -1
  33. package/dist/types/modes/types.d.ts +3 -0
  34. package/dist/types/modes/utils/injected-user-submission.d.ts +52 -0
  35. package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
  36. package/dist/types/notifications/config-commands.d.ts +9 -0
  37. package/dist/types/notifications/config.d.ts +22 -0
  38. package/dist/types/notifications/index.d.ts +2 -0
  39. package/dist/types/notifications/lifecycle-commands.d.ts +1 -0
  40. package/dist/types/notifications/lifecycle-control-runtime.d.ts +2 -1
  41. package/dist/types/notifications/reply-sent-store.d.ts +53 -0
  42. package/dist/types/notifications/rich-draft.d.ts +68 -0
  43. package/dist/types/notifications/rich-render.d.ts +90 -0
  44. package/dist/types/notifications/telegram-daemon-cli.d.ts +3 -3
  45. package/dist/types/notifications/telegram-daemon.d.ts +30 -0
  46. package/dist/types/notifications/telegram-reference.d.ts +7 -0
  47. package/dist/types/notifications/threaded-render.d.ts +14 -0
  48. package/dist/types/sdk.d.ts +3 -0
  49. package/dist/types/session/agent-session.d.ts +43 -1
  50. package/dist/types/session/file-mention-pruning.d.ts +8 -0
  51. package/dist/types/session/messages.d.ts +4 -0
  52. package/dist/types/session/session-manager.d.ts +17 -1
  53. package/dist/types/skill-state/active-state.d.ts +18 -1
  54. package/dist/types/system-prompt.d.ts +18 -2
  55. package/dist/types/task/executor.d.ts +1 -0
  56. package/dist/types/task/fork-context-budget.d.ts +7 -0
  57. package/dist/types/task/token-log.d.ts +30 -0
  58. package/dist/types/task/token-log.test.d.ts +1 -0
  59. package/dist/types/tools/bisect.d.ts +81 -0
  60. package/dist/types/tools/index.d.ts +1 -0
  61. package/dist/types/tools/search.d.ts +2 -2
  62. package/dist/types/utils/file-mentions.d.ts +14 -0
  63. package/dist/types/utils/git.d.ts +32 -0
  64. package/dist/types/utils/pasted-image-path.d.ts +30 -0
  65. package/dist/types/web/scrapers/youtube.d.ts +8 -0
  66. package/package.json +8 -11
  67. package/src/append-only-mode.ts +58 -0
  68. package/src/autoresearch/prompt.md +1 -1
  69. package/src/autoresearch/tools/log-experiment.ts +1 -1
  70. package/src/capability/rule.ts +9 -0
  71. package/src/cli/config-cli.ts +27 -3
  72. package/src/cli/fixture-report.test.ts +230 -0
  73. package/src/cli/fixture-report.ts +171 -0
  74. package/src/cli/nofile-limit.ts +48 -0
  75. package/src/cli/notify-cli.ts +11 -9
  76. package/src/cli/update-cli.ts +85 -9
  77. package/src/cli.ts +26 -0
  78. package/src/commands/contribution-prep.ts +1 -1
  79. package/src/config/keybindings.ts +1 -1
  80. package/src/config/settings-schema.ts +76 -1
  81. package/src/defaults/gjc/skills/deep-interview/SKILL.md +12 -13
  82. package/src/defaults/gjc/skills/ralplan/SKILL.md +30 -93
  83. package/src/defaults/gjc/skills/team/SKILL.md +16 -13
  84. package/src/defaults/gjc/skills/ultragoal/SKILL.md +59 -4
  85. package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +30 -0
  86. package/src/defaults/gjc-defaults.ts +9 -0
  87. package/src/discovery/helpers.ts +10 -0
  88. package/src/discovery/index.ts +1 -0
  89. package/src/export/html/template.generated.ts +1 -1
  90. package/src/export/html/template.js +60 -2
  91. package/src/export/ttsr.ts +96 -18
  92. package/src/extensibility/extensions/runner.ts +181 -219
  93. package/src/extensibility/gjc-plugins/runtime-adapters.ts +156 -44
  94. package/src/gjc-runtime/deep-interview-state.ts +31 -2
  95. package/src/gjc-runtime/launch-tmux.ts +6 -0
  96. package/src/gjc-runtime/session-state-sidecar.ts +208 -13
  97. package/src/gjc-runtime/state-writer.ts +15 -0
  98. package/src/gjc-runtime/team-runtime.ts +175 -19
  99. package/src/gjc-runtime/tmux-common.ts +1 -0
  100. package/src/gjc-runtime/ultragoal-guard.ts +55 -66
  101. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +315 -0
  102. package/src/gjc-runtime/ultragoal-runtime.ts +1602 -143
  103. package/src/gjc-runtime/workflow-manifest.generated.json +113 -2
  104. package/src/gjc-runtime/workflow-manifest.ts +74 -1
  105. package/src/goals/runtime.ts +10 -2
  106. package/src/hindsight/backend.ts +1 -3
  107. package/src/hooks/native-skill-hook.ts +95 -4
  108. package/src/internal-urls/docs-index.generated.ts +18 -9
  109. package/src/lsp/client.ts +1 -0
  110. package/src/main.ts +43 -2
  111. package/src/memories/index.ts +12 -1
  112. package/src/modes/components/assistant-message.ts +241 -82
  113. package/src/modes/components/custom-editor.ts +1 -0
  114. package/src/modes/components/footer.ts +7 -2
  115. package/src/modes/components/status-line.ts +167 -54
  116. package/src/modes/components/thinking-selector.ts +10 -8
  117. package/src/modes/components/tool-execution.ts +44 -19
  118. package/src/modes/controllers/command-controller.ts +39 -2
  119. package/src/modes/controllers/event-controller.ts +77 -49
  120. package/src/modes/controllers/extension-ui-controller.ts +8 -1
  121. package/src/modes/controllers/input-controller.ts +13 -18
  122. package/src/modes/controllers/selector-controller.ts +41 -0
  123. package/src/modes/interactive-mode.ts +14 -1
  124. package/src/modes/rpc/rpc-mode.ts +32 -12
  125. package/src/modes/rpc/rpc-socket-security.ts +13 -1
  126. package/src/modes/rpc/rpc-types.ts +9 -0
  127. package/src/modes/shared/agent-wire/command-validation.ts +4 -0
  128. package/src/modes/shared/agent-wire/event-contract.ts +22 -0
  129. package/src/modes/shared/agent-wire/event-envelope.ts +82 -0
  130. package/src/modes/shared/agent-wire/scopes.ts +1 -0
  131. package/src/modes/types.ts +3 -0
  132. package/src/modes/utils/injected-user-submission.ts +94 -0
  133. package/src/modes/utils/ui-helpers.ts +17 -4
  134. package/src/notifications/config-commands.ts +20 -0
  135. package/src/notifications/config.ts +43 -4
  136. package/src/notifications/index.ts +105 -9
  137. package/src/notifications/lifecycle-commands.ts +37 -16
  138. package/src/notifications/lifecycle-control-runtime.ts +8 -3
  139. package/src/notifications/reply-sent-store.ts +134 -0
  140. package/src/notifications/rich-draft.ts +107 -0
  141. package/src/notifications/rich-render.ts +142 -0
  142. package/src/notifications/telegram-daemon-cli.ts +54 -7
  143. package/src/notifications/telegram-daemon-control.ts +2 -2
  144. package/src/notifications/telegram-daemon.ts +390 -56
  145. package/src/notifications/telegram-reference.ts +17 -0
  146. package/src/notifications/threaded-render.ts +45 -2
  147. package/src/prompts/agents/architect.md +19 -10
  148. package/src/prompts/agents/critic.md +16 -18
  149. package/src/prompts/agents/executor.md +14 -11
  150. package/src/prompts/agents/planner.md +18 -13
  151. package/src/prompts/ci-green-request.md +3 -8
  152. package/src/prompts/goals/goal-continuation.md +0 -5
  153. package/src/prompts/goals/goal-mode-active.md +0 -5
  154. package/src/prompts/system/agent-creation-architect.md +2 -2
  155. package/src/prompts/system/plan-mode-active.md +1 -1
  156. package/src/prompts/system/plan-mode-subagent.md +3 -3
  157. package/src/prompts/system/project-prompt.md +0 -13
  158. package/src/prompts/system/subagent-system-prompt.md +3 -2
  159. package/src/prompts/system/system-prompt.md +26 -19
  160. package/src/prompts/system/volatile-project-context.md +10 -0
  161. package/src/prompts/system/web-search.md +4 -7
  162. package/src/prompts/tools/apply-patch.md +6 -0
  163. package/src/prompts/tools/ast-edit.md +1 -0
  164. package/src/prompts/tools/bash.md +1 -1
  165. package/src/prompts/tools/bisect.md +22 -0
  166. package/src/prompts/tools/browser.md +1 -1
  167. package/src/prompts/tools/cron.md +0 -2
  168. package/src/prompts/tools/eval.md +2 -2
  169. package/src/prompts/tools/find.md +1 -1
  170. package/src/prompts/tools/github.md +6 -6
  171. package/src/prompts/tools/goal.md +1 -3
  172. package/src/prompts/tools/image-gen.md +12 -5
  173. package/src/prompts/tools/irc.md +5 -4
  174. package/src/prompts/tools/job.md +2 -2
  175. package/src/prompts/tools/lsp.md +1 -1
  176. package/src/prompts/tools/monitor.md +2 -2
  177. package/src/prompts/tools/patch.md +2 -2
  178. package/src/prompts/tools/replace.md +2 -21
  179. package/src/prompts/tools/rewind.md +1 -1
  180. package/src/prompts/tools/search-tool-bm25.md +6 -11
  181. package/src/prompts/tools/search.md +1 -1
  182. package/src/prompts/tools/skill.md +3 -2
  183. package/src/prompts/tools/ssh.md +2 -1
  184. package/src/prompts/tools/subagent.md +2 -2
  185. package/src/prompts/tools/task.md +4 -5
  186. package/src/prompts/tools/todo-write.md +1 -1
  187. package/src/prompts/tools/vim.md +1 -0
  188. package/src/prompts/tools/web-search.md +2 -0
  189. package/src/prompts/tools/write.md +8 -2
  190. package/src/sdk.ts +68 -30
  191. package/src/session/agent-session.ts +734 -107
  192. package/src/session/file-mention-pruning.ts +64 -0
  193. package/src/session/messages.ts +4 -0
  194. package/src/session/session-manager.ts +59 -2
  195. package/src/skill-state/active-state.ts +141 -5
  196. package/src/skill-state/deep-interview-mutation-guard.ts +67 -3
  197. package/src/slash-commands/builtin-registry.ts +143 -1
  198. package/src/system-prompt.ts +57 -16
  199. package/src/task/executor.ts +125 -20
  200. package/src/task/fork-context-advisory.ts +2 -7
  201. package/src/task/fork-context-budget.ts +9 -0
  202. package/src/task/index.ts +4 -3
  203. package/src/task/output-manager.ts +4 -2
  204. package/src/task/token-log.test.ts +188 -0
  205. package/src/task/token-log.ts +179 -0
  206. package/src/tools/bisect.ts +367 -0
  207. package/src/tools/index.ts +3 -0
  208. package/src/tools/output-meta.ts +116 -4
  209. package/src/tools/search.ts +8 -2
  210. package/src/tools/skill.ts +15 -0
  211. package/src/utils/file-mentions.ts +39 -7
  212. package/src/utils/git.ts +73 -0
  213. package/src/utils/pasted-image-path.ts +170 -0
  214. package/src/web/scrapers/youtube.ts +24 -12
@@ -100,6 +100,7 @@ export interface InteractiveModeContext {
100
100
  onInputCallback?: (input: SubmittedUserInput) => void;
101
101
  optimisticUserMessageSignature: string | undefined;
102
102
  locallySubmittedUserSignatures: Set<string>;
103
+ optimisticInjectedSignatures: Map<string, number>;
103
104
  lastSigintTime: number;
104
105
  lastEscapeTime: number;
105
106
  lastComposerClearEscapeTime: number;
@@ -198,6 +199,7 @@ export interface InteractiveModeContext {
198
199
  handleDumpCommand(): void;
199
200
  handleDebugTranscriptCommand(): Promise<void>;
200
201
  handleClearCommand(): Promise<void>;
202
+ handleContextClearCommand(): Promise<void>;
201
203
  handleDropCommand(): Promise<void>;
202
204
  handleForkCommand(): Promise<void>;
203
205
  handleBashCommand(command: string, excludeFromContext?: boolean): Promise<void>;
@@ -222,6 +224,7 @@ export interface InteractiveModeContext {
222
224
  showModelSelector(options?: {
223
225
  temporaryOnly?: boolean;
224
226
  }): void;
227
+ showEffortSelector(): void;
225
228
  showProviderOnboarding(): void;
226
229
  showPluginSelector(mode?: "install" | "uninstall"): void;
227
230
  showUserMessageSelector(): void;
@@ -0,0 +1,52 @@
1
+ import type { ImageContent, TextContent } from "@gajae-code/ai";
2
+ import type { InteractiveModeContext } from "../types";
3
+ /**
4
+ * Normalize the content passed to an extension `sendUserMessage` call into a
5
+ * plain text string plus its image attachments. Mirrors the normalization in
6
+ * `AgentSession.sendUserMessage` (text parts joined with "\n") so the resulting
7
+ * text matches the eventual user `message_start` payload.
8
+ */
9
+ export declare function normalizeInjectedUserContent(content: string | (TextContent | ImageContent)[]): {
10
+ text: string;
11
+ images: ImageContent[];
12
+ imageCount: number;
13
+ };
14
+ /**
15
+ * Record a remotely/programmatically injected user message (e.g. Telegram
16
+ * inbound routed through the extension API) into the interactive TUI, so it is
17
+ * captured in prompt history and shown immediately instead of only appearing
18
+ * once the eventual `message_start` event lands.
19
+ *
20
+ * Local TUI submissions never reach this path (they go through
21
+ * `session.prompt(...)` / `startPendingSubmission`), so this cannot double-add
22
+ * local prompt history.
23
+ *
24
+ * - Always adds the injected text to editor prompt history.
25
+ * - Idle injections optimistically render the user message and record a pending
26
+ * injected optimistic signature (a counting Map, so multiple idle injections
27
+ * before the first `message_start` do not clobber each other); the later user
28
+ * `message_start` consumes one count and skips both the duplicate chat add and
29
+ * the defensive editor clear (so a locally typed draft is preserved).
30
+ * - Busy/queued injections refresh the pending-message display, which the
31
+ * caller has already populated by invoking `session.sendUserMessage(...)`
32
+ * before this helper.
33
+ *
34
+ * This helper never clears the editor text.
35
+ */
36
+ export declare function applyInjectedUserSubmission(ctx: InteractiveModeContext, input: {
37
+ content: string | (TextContent | ImageContent)[];
38
+ queued: boolean;
39
+ }): void;
40
+ /**
41
+ * Record one pending optimistic render for an injected user message.
42
+ *
43
+ * Injected sends are fire-and-forget and multiple idle injections can be
44
+ * rendered before the first `message_start` arrives, so a counting Map (not a
45
+ * single slot) tracks how many optimistic renders are outstanding per signature.
46
+ */
47
+ export declare function incrementInjectedOptimisticSignature(ctx: InteractiveModeContext, signature: string): void;
48
+ /**
49
+ * Consume one pending injected optimistic render for `signature`. Decrements the
50
+ * count (deleting the key at zero) and returns whether one was outstanding.
51
+ */
52
+ export declare function consumeInjectedOptimisticSignature(ctx: InteractiveModeContext, signature: string): boolean;
@@ -6,6 +6,7 @@ import type { SessionContext } from "../../session/session-manager";
6
6
  interface RenderInitialMessagesOptions {
7
7
  preserveExistingChat?: boolean;
8
8
  }
9
+ export declare function argsWithPartialJson(args: unknown, partialJson: unknown): unknown;
9
10
  export declare class UiHelpers {
10
11
  #private;
11
12
  private ctx;
@@ -24,3 +24,12 @@ export interface ConfigCommandChange {
24
24
  * can fall through to treating it as a free-text injection).
25
25
  */
26
26
  export declare function parseInThreadConfigCommand(text: string): ConfigCommandChange | undefined;
27
+ /**
28
+ * Parse a `/rich on|off` toggle. Returns `true`/`false` for a recognised
29
+ * on/off argument, or `undefined` otherwise (not a `/rich` command, or `/rich`
30
+ * with a missing/invalid argument). This is intentionally SEPARATE from
31
+ * `parseInThreadConfigCommand`: `/verbose`/`/redact` are producer/session config
32
+ * forwarded over the WS, whereas rich is Telegram-daemon delivery policy handled
33
+ * daemon-locally, so it never becomes a `config_command` frame or a user turn.
34
+ */
35
+ export declare function parseRichToggleCommand(text: string): boolean | undefined;
@@ -14,11 +14,33 @@ export interface NotificationConfig {
14
14
  redact: boolean;
15
15
  verbosity: "lean" | "verbose";
16
16
  idleTimeoutMs: number;
17
+ rich: {
18
+ enabled: boolean;
19
+ };
20
+ richDraft: {
21
+ enabled: boolean;
22
+ };
17
23
  }
18
24
  /** Read typed config from Settings. */
19
25
  export declare function getNotificationConfig(settings: Settings): NotificationConfig;
26
+ export declare function hasNonBlankValue(value: string | undefined): boolean;
27
+ /** Is Telegram configured with usable non-blank boundary credentials? */
28
+ export declare function isTelegramConfigured(cfg: NotificationConfig): cfg is NotificationConfig & {
29
+ botToken: string;
30
+ chatId: string;
31
+ };
20
32
  /** Is global config sufficient for auto-on (enabled + at least one configured adapter)? */
21
33
  export declare function isGloballyConfigured(cfg: NotificationConfig): boolean;
34
+ /**
35
+ * Per-run opt-out for completion notifications, honored before settings lookups.
36
+ *
37
+ * `GJC_NOTIFY=off` (also `0` / `false`, case-insensitive) suppresses the
38
+ * completion notification surface for this process only. `config.yml` is
39
+ * untouched and child processes inherit the env var, which lets non-interactive
40
+ * fleet runs (`gjc -p --no-session`) stay silent even when a user-level/global
41
+ * completion notification configuration is enabled.
42
+ */
43
+ export declare function completionNotifyDisabledByEnv(env: NodeJS.ProcessEnv): boolean;
22
44
  /** Resolve whether the notifications extension should be registered at SDK startup. */
23
45
  export declare function shouldRegisterNotificationsExtension(input: {
24
46
  env: NodeJS.ProcessEnv;
@@ -61,6 +61,8 @@ export interface SessionCreateFrame {
61
61
  target: SessionCreateTarget;
62
62
  /** Reference to the daemon-written, once-consumed startup-prompt file. */
63
63
  startupPromptRef?: string;
64
+ /** Model profile preset to activate for the spawned session (--mpreset). */
65
+ modelPreset?: string;
64
66
  }
65
67
  /** Close (hard-kill, history preserved) a session. */
66
68
  export interface SessionCloseFrame {
@@ -4,6 +4,7 @@ export type LifecycleCommandVerb = "session_create" | "session_close" | "session
4
4
  export type ParsedLifecycleCommand = {
5
5
  kind: "create";
6
6
  target: SessionCreateTarget;
7
+ modelPreset?: string;
7
8
  } | {
8
9
  kind: "close";
9
10
  target: SessionCloseTarget;
@@ -35,7 +35,8 @@ export declare function createRateLimiter(maxPerWindow: number, windowMs: number
35
35
  * The launched session id is carried via `GJC_SESSION_ID` in the child env (see
36
36
  * {@link daemonSpawnCreate}); the root `gjc` launcher has no `--session-id`
37
37
  * flag, so it must never appear in argv. Only flags the launch parser actually
38
- * supports are emitted (`--worktree <branch>` for worktree targets). */
38
+ * supports are emitted (`--worktree <branch>` for worktree targets,
39
+ * `--mpreset <profile>` for model presets). */
39
40
  export declare function buildCreateArgv(frame: SessionCreateFrame, _ids: {
40
41
  intendedSessionId: string;
41
42
  startupPromptRef?: string;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Index of rich messages the daemon sent, mapping `${chat_id}:${message_id}` to
3
+ * the original markdown. Telegram does not echo a `sendRichMessage` message's
4
+ * text in a reply's `reply_to_message`, so a user replying to a rich final
5
+ * answer would otherwise strand the agent without the quoted context. The daemon
6
+ * records the markdown on every promoted send and looks it up on inbound replies
7
+ * to restore that context.
8
+ *
9
+ * Fail-closed and off-path-neutral: nothing is recorded unless a send was
10
+ * actually promoted to rich, `record` is a no-op on any error (the in-memory map
11
+ * is only mutated after the atomic persist succeeds), and `lookup` returns
12
+ * undefined on any miss or error, so a bad index write/read never kills the
13
+ * daemon.
14
+ */
15
+ import * as fs from "node:fs";
16
+ /**
17
+ * Minimal async filesystem surface the store needs. Structurally a subset of the
18
+ * daemon's `TelegramDaemonFs`, so the daemon can pass its own `fs` straight
19
+ * through and tests can inject a fake.
20
+ */
21
+ export interface ReplySentStoreFs {
22
+ mkdir(path: string, opts?: fs.MakeDirectoryOptions): Promise<unknown>;
23
+ readFile(path: string, encoding: BufferEncoding): Promise<string>;
24
+ writeFile(path: string, data: string, opts?: fs.WriteFileOptions): Promise<void>;
25
+ rename(oldPath: string, newPath: string): Promise<void>;
26
+ chmod(path: string, mode: number): Promise<void>;
27
+ }
28
+ export declare class ReplySentStore {
29
+ #private;
30
+ constructor(input: {
31
+ agentDir: string;
32
+ fs?: ReplySentStoreFs;
33
+ now?: () => number;
34
+ });
35
+ /** Restore the persisted index into memory. No-op on a missing or corrupt file. */
36
+ load(): Promise<void>;
37
+ /**
38
+ * Record the original markdown of a rich message the daemon just sent. The
39
+ * text is capped at {@link MAX_TEXT_LENGTH}; the index is capped at
40
+ * {@link MAX_ENTRIES} (oldest-by-timestamp evicted). No-op on any failure: the
41
+ * in-memory map is only replaced after the atomic persist succeeds.
42
+ */
43
+ record(input: {
44
+ chatId: string | number;
45
+ messageId: number;
46
+ text: string;
47
+ }): Promise<void>;
48
+ /** Look up the original markdown for a message the daemon sent. undefined on miss or failure. */
49
+ lookup(input: {
50
+ chatId: string | number;
51
+ messageId: number;
52
+ }): string | undefined;
53
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Opt-in rich-draft streaming for a turn's in-progress preview.
3
+ *
4
+ * When the operator opts in (`richDraft.enabled`) the daemon streams LIVE turn
5
+ * markdown to the Bot API `sendRichMessageDraft` method as a debounced preview.
6
+ * Every failure is a harmless no-op (warn once, keep the daemon alive), and when
7
+ * off no draft call is ever made — so the off-state request bodies stay
8
+ * byte-identical.
9
+ */
10
+ import type { BotApi } from "./telegram-daemon";
11
+ import type { ThreadedSend } from "./threaded-render";
12
+ /** Minimum gap between two draft sends for one session (debounce floor). */
13
+ export declare const DRAFT_DEBOUNCE_MS = 1500;
14
+ /**
15
+ * Wrap raw markdown + a monotonic draft id in the `sendRichMessageDraft` request
16
+ * payload shape: mirrors `buildRichMessage`'s proven `rich_message.markdown`
17
+ * content wrapper, with a top-level `draft_id` selecting the draft revision to
18
+ * update (a routing param, like `message_thread_id`).
19
+ */
20
+ export declare function buildRichDraft(draftId: number, raw: string): {
21
+ draft_id: number;
22
+ rich_message: {
23
+ markdown: string;
24
+ };
25
+ };
26
+ /**
27
+ * Whether a granted send should stream a rich draft. Fail-closed: every clause
28
+ * must hold, otherwise no draft is sent. Mirrors `shouldPromoteRich` but targets
29
+ * the LIVE lane and the live-only `richDraftMarkdown` marker (set by
30
+ * `renderThreadedFrame` for non-finalized turn frames).
31
+ */
32
+ export declare function shouldStreamDraft(input: {
33
+ enabled?: boolean;
34
+ send: ThreadedSend;
35
+ }): boolean;
36
+ /**
37
+ * Per-session debounce + monotonic draft-id state for draft streaming. Skips a
38
+ * draft when less than `debounceMs` has elapsed since the session's last SENT
39
+ * draft (the rate-limit pool already coalesces live frames to the latest, so a
40
+ * skipped frame is naturally superseded by the next one). `reset` clears a
41
+ * session's window when its turn finalizes so the next turn starts fresh.
42
+ */
43
+ export declare class DraftStreamState {
44
+ #private;
45
+ constructor(debounceMs?: number);
46
+ /**
47
+ * If enough time has elapsed since the session's last draft, record `now` as
48
+ * the new last-sent time and return the next monotonic draft id; otherwise
49
+ * return `undefined` (debounced — the caller skips this frame).
50
+ */
51
+ tryClaim(sessionId: string, now: number): number | undefined;
52
+ /** Clear a session's debounce window (called when its turn finalizes). The
53
+ * global draft id keeps incrementing so ids are never reused across turns. */
54
+ reset(sessionId: string): void;
55
+ }
56
+ /**
57
+ * Best-effort delivery of a rich draft. Never throws and has no HTML fallback: a
58
+ * draft is a purely additive preview, so on any failure (a thrown transport
59
+ * error or an `{ ok: false }` JSON response — the transport returns `res.json()`
60
+ * for JSON methods, so `ok:false` does not throw) it warns exactly once and
61
+ * returns; the unchanged live HTML send still carries the content.
62
+ */
63
+ export declare function deliverDraft(botApi: BotApi, base: {
64
+ chat_id: string | number;
65
+ message_thread_id?: number;
66
+ }, draftId: number, raw: string, log?: {
67
+ warn(msg: string): void;
68
+ }): Promise<void>;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Rich-message promotion for stable non-editable Telegram text sends.
3
+ *
4
+ * When enabled, the daemon promotes eligible finalized `sendMessage` payloads
5
+ * carrying raw markdown to the Bot API `sendRichMessage` method. On any miss or
6
+ * failure the daemon keeps the unchanged HTML `sendMessage` path, so the
7
+ * off-state request bodies are byte-identical.
8
+ */
9
+ import type { BotApi } from "./telegram-daemon";
10
+ import type { ThreadedSend } from "./threaded-render";
11
+ /**
12
+ * Telegram's hard per-message character ceiling (4096). Surfaced here purely as
13
+ * documentation and a marker for a future native rich-message splitter — it is
14
+ * intentionally NON-BEHAVIORAL and MUST stay that way: nothing in the rich path
15
+ * branches on this value.
16
+ *
17
+ * Overflow is already safe without it. The production final-answer text is capped
18
+ * at 3500 chars upstream (`summaryFromMessage(..., 3500)`), so a promoted
19
+ * `sendRichMessage` never approaches this ceiling; and if the Bot API ever rejects
20
+ * an oversized rich payload it returns `{ ok: false }`, which
21
+ * `deliverRichWithFallback` (below) turns into the chunked HTML `splitTelegramHtml`
22
+ * fallback (each chunk ≤ TELEGRAM_MESSAGE_LIMIT). This constant only marks where a
23
+ * future rich splitter would read its ceiling; wiring it into a branch would change
24
+ * byte-for-byte behavior and is out of scope.
25
+ */
26
+ export declare const RICH_MESSAGE_LIMIT = 4096;
27
+ /** Wrap raw markdown in the `sendRichMessage` request payload shape. */
28
+ export declare function buildRichMessage(raw: string, extras?: {
29
+ reply_markup?: unknown;
30
+ }): {
31
+ rich_message: {
32
+ markdown: string;
33
+ };
34
+ reply_markup?: unknown;
35
+ };
36
+ /**
37
+ * Whether a granted send should be promoted to `sendRichMessage`. Fail-closed
38
+ * and class-aware: every clause must hold, otherwise the daemon keeps the HTML path.
39
+ */
40
+ export declare function shouldPromoteRich(input: {
41
+ enabled?: boolean;
42
+ send: ThreadedSend;
43
+ }): boolean;
44
+ /**
45
+ * Deliver the promoted rich message, falling back to `fallbackDeliver` (the
46
+ * unchanged HTML `sendMessage` loop) on any failure. A failure is either a
47
+ * thrown transport error or a `{ ok: false }` JSON response (the transport
48
+ * returns `res.json()` for JSON methods, so `ok:false` does not throw). On
49
+ * failure exactly one diagnostic is logged before the fallback runs; on success
50
+ * the fallback never runs.
51
+ *
52
+ * Returns the sent message's `message_id` on success (when the response carries
53
+ * one), otherwise `undefined` — including every failure/fallback path and a
54
+ * success whose response omits `result.message_id`. Callers that ignore the
55
+ * return value are unaffected.
56
+ */
57
+ export declare function deliverRichWithFallback(botApi: BotApi, base: {
58
+ chat_id: string | number;
59
+ message_thread_id?: number;
60
+ }, send: ThreadedSend, fallbackDeliver: () => Promise<void>, log?: {
61
+ warn(msg: string): void;
62
+ }): Promise<number | undefined>;
63
+ /**
64
+ * Deliver an action-needed (ask/idle) message via `sendRichMessage`, falling
65
+ * back to the unchanged HTML chunk loop on any failure. Mirrors
66
+ * {@link deliverRichWithFallback} but takes an explicit markdown body plus an
67
+ * optional top-level `reply_markup` (probe-confirmed: `sendRichMessage` accepts
68
+ * `reply_markup` alongside `rich_message`), and surfaces a structured outcome so
69
+ * the daemon can route inbound replies to the resulting message id.
70
+ *
71
+ * On rich success: returns `{ messageId, usedRich: true, usedFallback: false }`
72
+ * where `messageId` is `res.result.message_id` when present. On a `{ ok:false }`
73
+ * response or a thrown transport error: warns exactly once, runs `htmlFallback`,
74
+ * and returns `{ messageId, usedRich: false, usedFallback: true }` where
75
+ * `messageId` is the fallback's return value (the last HTML chunk's id).
76
+ */
77
+ export declare function deliverRichActionWithFallback(botApi: BotApi, base: {
78
+ chat_id: string | number;
79
+ message_thread_id?: number;
80
+ }, opts: {
81
+ markdown: string;
82
+ replyMarkup?: unknown;
83
+ requireMessageId?: boolean;
84
+ }, htmlFallback: () => Promise<number | undefined>, log?: {
85
+ warn(msg: string): void;
86
+ }): Promise<{
87
+ messageId?: number;
88
+ usedRich: boolean;
89
+ usedFallback: boolean;
90
+ }>;
@@ -9,7 +9,7 @@ export interface RunDaemonInternalDeps {
9
9
  SettingsImpl?: {
10
10
  init: (options?: {
11
11
  agentDir?: string;
12
- }) => Promise<Pick<Settings, "get" | "getAgentDir">>;
12
+ }) => Promise<Pick<Settings, "get" | "getAgentDir" | "set">>;
13
13
  };
14
14
  DaemonImpl?: TelegramDaemonConstructor;
15
15
  processPid?: number;
@@ -18,8 +18,8 @@ export interface RunDaemonInternalDeps {
18
18
  export declare function createLightweightDaemonSettings(input: {
19
19
  agentDir: string;
20
20
  rawConfig?: unknown;
21
- }): Pick<Settings, "get" | "getAgentDir">;
22
- export declare function loadLightweightDaemonSettings(agentDir: string): Promise<Pick<Settings, "get" | "getAgentDir">>;
21
+ }): Pick<Settings, "get" | "getAgentDir" | "set">;
22
+ export declare function loadLightweightDaemonSettings(agentDir: string): Promise<Pick<Settings, "get" | "getAgentDir" | "set">>;
23
23
  export declare function ownerPidFromOwnerId(ownerId: string): number | undefined;
24
24
  export declare function runDaemonSmoke(opts?: {
25
25
  agentDir?: string;
@@ -231,6 +231,14 @@ export interface TelegramDaemonOptions {
231
231
  * default applies (e.g. lifecycle control disabled), no control server starts.
232
232
  */
233
233
  createLifecycleControlServer?: LifecycleControlServerFactory | null;
234
+ /** Rich text promotion (enabled by default; see rich-render.ts). */
235
+ rich?: {
236
+ enabled: boolean;
237
+ };
238
+ /** Opt-in rich-draft streaming of live turn previews (off by default; see rich-draft.ts). */
239
+ richDraft?: {
240
+ enabled: boolean;
241
+ };
234
242
  }
235
243
  interface SessionSocket {
236
244
  sessionId: string;
@@ -254,6 +262,8 @@ export declare class TelegramNotificationDaemon {
254
262
  private readonly opts;
255
263
  readonly aliasTable: AliasTable;
256
264
  readonly messageRoutes: Map<string | number, CallbackRoute | Omit<CallbackRoute, "answer">>;
265
+ /** Telegram message id backing each streamed `${sessionId}:${coalesceKey}`, for in-place edits. */
266
+ private readonly liveMessages;
257
267
  readonly sessions: Map<string, SessionSocket>;
258
268
  private readonly runtime;
259
269
  private readonly sessionRouter;
@@ -266,6 +276,10 @@ export declare class TelegramNotificationDaemon {
266
276
  private readonly pool;
267
277
  private readonly poller;
268
278
  private readonly dispatchState;
279
+ /** Original markdown of rich messages we sent (chat+message_id), for restoring reply context on inbound replies. */
280
+ private readonly replyStore;
281
+ /** Per-session debounce + monotonic draft-id state for opt-in draft streaming. */
282
+ private readonly draftStream;
269
283
  /** Identity-bearing sessions by repo/branch surface, used to avoid transient duplicate topics. */
270
284
  private readonly topicOwnerByIdentity;
271
285
  /** Non-identity frames held until identity creates the correct thread. */
@@ -374,7 +388,23 @@ export declare class TelegramNotificationDaemon {
374
388
  private ensureAttachmentDir;
375
389
  private cleanupAllAttachmentDirs;
376
390
  private resolveInboundAttachment;
391
+ /**
392
+ * Serialize all pool flushes. Every caller (`submitThreadedFrame`, the flat
393
+ * fallback, the drain timer's `void this.flushPool()`, topic teardown) goes
394
+ * through one promise chain, so two flushes never interleave — a live send can
395
+ * never be in-flight while a finalized flush reads `liveMessages` and decides
396
+ * to post a fresh (duplicate) final. Errors are swallowed so one failed flush
397
+ * never poisons the queue (each flush is already best-effort internally).
398
+ */
399
+ private flushChain;
377
400
  private flushPool;
401
+ private flushPoolInner;
402
+ /**
403
+ * Track the Telegram message id backing a streamed `(sessionId, coalesceKey)`
404
+ * so later live/finalized frames edit it in place. Evicts this session's stale
405
+ * same-category entries (e.g. prior turns) so the map stays bounded.
406
+ */
407
+ private recordLiveMessage;
378
408
  private deliverFlatFallback;
379
409
  private pairedChatIsPrivate;
380
410
  private notifyThreadedFallback;
@@ -59,6 +59,13 @@ export declare function buildActionMessage(action: {
59
59
  options?: string[];
60
60
  summary?: string;
61
61
  }): RenderedMessage;
62
+ /** Render an `action_needed` body as raw markdown (rich-message source; the HTML fallback stays on buildActionMessage). */
63
+ export declare function buildActionMarkdown(action: {
64
+ kind: "ask" | "idle";
65
+ question?: string;
66
+ options?: string[];
67
+ summary?: string;
68
+ }): string;
62
69
  /** Send Telegram HTML text chunks sequentially so long messages preserve order. */
63
70
  export declare function sendTelegramHtmlChunks(send: TelegramSend, chatId: string, text: string, inlineKeyboard?: InlineButton[][]): Promise<void>;
64
71
  /** A protocol `reply` frame the client should send to the server. */
@@ -28,6 +28,19 @@ export interface ThreadedSend {
28
28
  coalesceKey?: string;
29
29
  /** True for the one-time identity header (the daemon pins it once). */
30
30
  identity?: boolean;
31
+ /**
32
+ * When true the daemon may deliver this as an in-place edit of the message
33
+ * previously sent under the same `(sessionId, coalesceKey)` instead of a new
34
+ * message. Set for streamed turn frames so live + finalized share one message.
35
+ */
36
+ editable?: boolean;
37
+ /** Rich message class metadata. Only finalized final-answer sends are ever
38
+ * rich-promoted; the daemon gate (`shouldPromoteRich`) requires exactly this. */
39
+ richClass?: "final";
40
+ /** Rich final-answer markdown (raw). Delivery marker derived ONLY from a frame's `finalAnswer` bit; never inferred from `phase`. */
41
+ richMarkdown?: string;
42
+ /** Live-turn raw markdown for opt-in draft streaming (set ONLY on non-finalized turn frames; never triggers rich-final promotion, which requires `lane === "finalized"`). */
43
+ richDraftMarkdown?: string;
31
44
  }
32
45
  interface ThreadedFrame {
33
46
  type?: unknown;
@@ -44,6 +57,7 @@ interface ThreadedFrame {
44
57
  diff?: unknown;
45
58
  cwd?: unknown;
46
59
  phase?: unknown;
60
+ finalAnswer?: boolean;
47
61
  text?: unknown;
48
62
  messageRef?: unknown;
49
63
  source?: unknown;
@@ -1,5 +1,6 @@
1
1
  import { type AgentTelemetryConfig, type ThinkingLevel } from "@gajae-code/agent-core";
2
2
  import { type Model, type ProviderSessionState } from "@gajae-code/ai";
3
+ import { createAppendOnlyContextManager, providerSupportsAppendOnlyAuto, resolveAppendOnlyMode } from "./append-only-mode";
3
4
  import { type Rule } from "./capability/rule";
4
5
  import { ModelRegistry } from "./config/model-registry";
5
6
  import { type ScopedModelSelection } from "./config/model-resolver";
@@ -241,5 +242,7 @@ export interface BuildSystemPromptOptions {
241
242
  * as separate entries so providers can cache prompt prefixes without concatenating blocks.
242
243
  */
243
244
  export declare function buildSystemPrompt(options?: BuildSystemPromptOptions): Promise<BuildSystemPromptResult>;
245
+ export { createAppendOnlyContextManager, providerSupportsAppendOnlyAuto, resolveAppendOnlyMode };
244
246
  export declare function createPluginHooksExtension(hooks: ConstrainedPluginHook[]): ExtensionFactory;
247
+ export declare function resolveIntentTracingEnabled(intentTracingSetting: boolean | undefined, hasUI: boolean): boolean;
245
248
  export declare function createAgentSession(options?: CreateAgentSessionOptions): Promise<CreateAgentSessionResult>;
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import { type Agent, type AgentEvent, type AgentMessage, type AgentState, type AgentTool, type StablePrefixSnapshot, ThinkingLevel } from "@gajae-code/agent-core";
16
16
  import { type CompactionResult, type EmergencyCompactionSample } from "@gajae-code/agent-core/compaction";
17
- import type { AssistantMessage, Effort, ImageContent, Message, MessageAttribution, Model, ProviderSessionState, ServiceTier, SimpleStreamOptions, TextContent, ToolChoice, UsageReport } from "@gajae-code/ai";
17
+ import type { AssistantMessage, Effort, ImageContent, Message, MessageAttribution, Model, ProviderSessionState, ServiceTier, SimpleStreamOptions, TextContent, ToolCall, ToolChoice, UsageReport } from "@gajae-code/ai";
18
18
  export interface ForkContextSeedMetadata {
19
19
  sourceSessionId: string;
20
20
  parentMessageCount: number;
@@ -75,6 +75,7 @@ import { type DiscoverableTool, type DiscoverableToolSearchIndex } from "../tool
75
75
  import type { AskAnswerSource, ToolSession } from "../tools";
76
76
  import type { CheckpointState } from "../tools/checkpoint";
77
77
  import { type TodoItem, type TodoPhase } from "../tools/todo-write";
78
+ import { type WorkspaceTree } from "../workspace-tree";
78
79
  import type { ClientBridge } from "./client-bridge";
79
80
  import { type ContributionPrepOptions, type ContributionPrepResult } from "./contribution-prep";
80
81
  import { type CustomMessage } from "./messages";
@@ -200,6 +201,8 @@ export interface AgentSessionConfig {
200
201
  rebuildSystemPrompt?: (toolNames: string[], tools: Map<string, AgentTool>) => Promise<{
201
202
  systemPrompt: string[];
202
203
  }>;
204
+ /** Initial workspace tree snapshot used for the first volatile per-turn context message. */
205
+ workspaceTree?: WorkspaceTree;
203
206
  /** Rebuild the SSH tool from current capability discovery results. */
204
207
  reloadSshTool?: () => Promise<AgentTool | null>;
205
208
  requestedToolNames?: ReadonlySet<string>;
@@ -336,6 +339,33 @@ export type BeforeAgentStartContributor = (event: {
336
339
  images?: ImageContent[];
337
340
  sessionId: string | undefined;
338
341
  }) => Promise<BeforeAgentStartInternalMessage | undefined>;
342
+ export declare class WorkerIntegrationRequestScheduler {
343
+ #private;
344
+ readonly request: () => Promise<void>;
345
+ constructor(request: () => Promise<void>);
346
+ enqueue(): void;
347
+ flush(): Promise<void>;
348
+ }
349
+ export type StreamingEditParsedToolCall = {
350
+ toolCall: ToolCall;
351
+ path: string;
352
+ resolvedPath: string;
353
+ diff?: string;
354
+ op?: string;
355
+ rename?: string;
356
+ };
357
+ export type StreamingEditParsedCacheEntry = {
358
+ version: string;
359
+ parsed: StreamingEditParsedToolCall | undefined;
360
+ };
361
+ export declare function getStreamingEditToolCallForEvent(event: AgentEvent, cache: Map<string, StreamingEditParsedCacheEntry>, resolvePath: (filePath: string) => string | undefined): StreamingEditParsedToolCall | undefined;
362
+ /** Test-only counters for AgentSession event fan-out hot-path assertions. */
363
+ export declare const __agentSessionPerfCounters: {
364
+ listenerSnapshotRebuilds: number;
365
+ messageUpdateExtensionQueues: number;
366
+ reset(): void;
367
+ };
368
+ export declare function buildContextInjectionSignature(kind: string, parts: readonly string[]): string;
339
369
  export declare class AgentSession {
340
370
  #private;
341
371
  readonly agent: Agent;
@@ -344,6 +374,13 @@ export declare class AgentSession {
344
374
  readonly taskDepth: number;
345
375
  readonly yieldQueue: YieldQueue;
346
376
  readonly configWarnings: string[];
377
+ readonly streamingEditDebugCounters: {
378
+ guardRuns: number;
379
+ processedChars: number;
380
+ checkedRemovedLines: number;
381
+ fullChecks: number;
382
+ nonEditDeterminations: number;
383
+ };
347
384
  readonly rawSseDebugBuffer: RawSseDebugBuffer;
348
385
  constructor(config: AgentSessionConfig);
349
386
  /** Model registry for API key resolution and model discovery */
@@ -716,6 +753,11 @@ export declare class AgentSession {
716
753
  * @returns true if completed, false if cancelled by hook
717
754
  */
718
755
  newSession(options?: NewSessionOptions): Promise<boolean>;
756
+ /**
757
+ * Clear active conversational/model context while preserving the current
758
+ * session identity and durable history trail.
759
+ */
760
+ clearContext(): Promise<boolean>;
719
761
  /**
720
762
  * Set a display name for the current session.
721
763
  */
@@ -0,0 +1,8 @@
1
+ import type { SessionEntry, SessionMessageEntry } from "./session-manager";
2
+ export interface FileMentionPruneResult {
3
+ changed: SessionMessageEntry[];
4
+ bytesSaved: number;
5
+ }
6
+ /** Notice injected in place of a superseded file-mention body. */
7
+ export declare function stalefileMentionNotice(path: string): string;
8
+ export declare function pruneStaleFileMentions(entries: readonly SessionEntry[], resolveAbs: (path: string) => string): FileMentionPruneResult;
@@ -129,6 +129,10 @@ export interface FileMentionMessage {
129
129
  /** Why the file contents were omitted from auto-read. */
130
130
  skippedReason?: "tooLarge";
131
131
  image?: ImageContent;
132
+ /** Set when this mention duplicated a recently shown path (compact note, no full body). */
133
+ duplicate?: boolean;
134
+ /** Set when a stale entry was pruned to a digest notice (never silently deleted). */
135
+ pruned?: boolean;
132
136
  }>;
133
137
  timestamp: number;
134
138
  }