@gajae-code/coding-agent 0.7.0 → 0.7.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.
- package/CHANGELOG.md +9 -0
- package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
- package/dist/types/gjc-runtime/tmux-common.d.ts +3 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +2 -0
- package/package.json +7 -7
- package/src/cli.ts +1 -3
- package/src/edit/modes/replace.ts +1 -1
- package/src/gjc-runtime/launch-tmux.ts +10 -2
- package/src/gjc-runtime/tmux-common.ts +8 -0
- package/src/gjc-runtime/tmux-sessions.ts +8 -1
- package/src/hashline/hash.ts +1 -1
- package/src/internal-urls/docs-index.generated.ts +5 -4
- package/src/notifications/index.ts +44 -7
- package/src/tools/ask.ts +3 -2
|
@@ -142,6 +142,11 @@ interface SessionRuntime {
|
|
|
142
142
|
busy: boolean;
|
|
143
143
|
/** Inbound Telegram update ids injected but not yet consumed by a turn. */
|
|
144
144
|
pendingInbound: Set<number>;
|
|
145
|
+
/** Latest assistant text of the in-flight turn (from message_update). */
|
|
146
|
+
currentTurnText?: string;
|
|
147
|
+
/** Assistant text already flushed before an ask this turn (turn-scoped dedupe
|
|
148
|
+
* so turn_end does not re-emit the pre-ask lead-in). Reset each turn. */
|
|
149
|
+
preAskFlushedText?: string;
|
|
145
150
|
}
|
|
146
151
|
|
|
147
152
|
interface ResolvedSettings {
|
|
@@ -620,18 +625,42 @@ export const createNotificationsExtension: ExtensionFactory = api => {
|
|
|
620
625
|
// Redaction suppresses streamed content (only the one-time identity header
|
|
621
626
|
// survives redaction). The daemon coalesces/throttles these via its shared
|
|
622
627
|
// rate-limit pool before sending to Telegram.
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
if (!rt) return;
|
|
627
|
-
|
|
628
|
-
const text = summaryFromMessage(event.message, 3500);
|
|
629
|
-
if (!text) return;
|
|
628
|
+
// Push the in-flight turn's assistant text as a finalized turn_stream, deduped
|
|
629
|
+
// against what was already flushed for this turn (the pre-ask lead-in).
|
|
630
|
+
const flushTurnText = (rt: SessionRuntime, id: string, text: string | undefined): void => {
|
|
631
|
+
if (!text || text === rt.preAskFlushedText) return;
|
|
632
|
+
rt.preAskFlushedText = text;
|
|
630
633
|
try {
|
|
631
634
|
rt.server.pushFrame(JSON.stringify({ type: "turn_stream", sessionId: id, phase: "finalized", text }));
|
|
632
635
|
} catch (e) {
|
|
633
636
|
logger.warn(`notifications: pushFrame (turn) failed: ${String(e)}`);
|
|
634
637
|
}
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
// Emit the assistant text that precedes an ask BEFORE the ask's action_needed
|
|
641
|
+
// is broadcast, so the remote (e.g. Telegram) shows the lead-in first instead
|
|
642
|
+
// of only after the ask resolves at turn_end. The text is captured on
|
|
643
|
+
// message_end (which, like tool_execution_start, is on the awaited extension
|
|
644
|
+
// path and ordered before it — unlike message_update, which is queued async),
|
|
645
|
+
// then flushed here before the ask tool's execute calls registerAsk.
|
|
646
|
+
api.on("tool_execution_start", (event, ctx) => {
|
|
647
|
+
if (event.toolName !== "ask") return;
|
|
648
|
+
const id = sessionId(ctx);
|
|
649
|
+
const rt = runtimes.get(id);
|
|
650
|
+
if (!rt || rt.redact) return;
|
|
651
|
+
flushTurnText(rt, id, rt.currentTurnText);
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
api.on("turn_end", (event, ctx) => {
|
|
655
|
+
const id = sessionId(ctx);
|
|
656
|
+
const rt = runtimes.get(id);
|
|
657
|
+
if (!rt) return;
|
|
658
|
+
const text = rt.redact ? undefined : summaryFromMessage(event.message, 3500);
|
|
659
|
+
if (text) flushTurnText(rt, id, text);
|
|
660
|
+
// Reset per-turn streaming state so the next turn starts fresh and a later
|
|
661
|
+
// turn with identical text is not falsely deduped.
|
|
662
|
+
rt.currentTurnText = undefined;
|
|
663
|
+
rt.preAskFlushedText = undefined;
|
|
635
664
|
});
|
|
636
665
|
|
|
637
666
|
// Stream agent-produced images (computer/browser/tool screenshots) as
|
|
@@ -640,6 +669,14 @@ export const createNotificationsExtension: ExtensionFactory = api => {
|
|
|
640
669
|
const id = sessionId(ctx);
|
|
641
670
|
const rt = runtimes.get(id);
|
|
642
671
|
if (!rt || rt.redact) return;
|
|
672
|
+
// Capture the in-flight ASSISTANT text here (message_end is on the awaited
|
|
673
|
+
// extension path and ordered before tool_execution_start) so the pre-ask
|
|
674
|
+
// flush can emit it before the ask prompt. Role-scoped: message_end also
|
|
675
|
+
// fires for the user prompt, which must never be mirrored back as turn output.
|
|
676
|
+
if ((event.message as { role?: unknown }).role === "assistant") {
|
|
677
|
+
const turnText = summaryFromMessage(event.message, 3500);
|
|
678
|
+
if (turnText) rt.currentTurnText = turnText;
|
|
679
|
+
}
|
|
643
680
|
for (const img of imageAttachmentsFromMessage(event.message)) {
|
|
644
681
|
try {
|
|
645
682
|
rt.server.pushFrame(
|
package/src/tools/ask.ts
CHANGED
|
@@ -675,8 +675,9 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
675
675
|
}
|
|
676
676
|
try {
|
|
677
677
|
const deepInterviewPrompt = formatDeepInterviewSelectorPrompt(q.question);
|
|
678
|
+
const isDeepInterviewQuestion = deepInterviewPrompt !== null || q.deepInterview !== undefined;
|
|
678
679
|
const displayQuestion = deepInterviewPrompt ?? q.question;
|
|
679
|
-
const shouldNumberOptions = isDeepInterviewAskQuestion(q.question);
|
|
680
|
+
const shouldNumberOptions = isDeepInterviewQuestion || isDeepInterviewAskQuestion(q.question);
|
|
680
681
|
const optionLabels = shouldNumberOptions ? numberOptionLabels(rawOptionLabels) : rawOptionLabels;
|
|
681
682
|
const initialSelection =
|
|
682
683
|
shouldNumberOptions && options?.previous
|
|
@@ -700,7 +701,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
700
701
|
signal,
|
|
701
702
|
initialSelection,
|
|
702
703
|
navigation: options?.navigation,
|
|
703
|
-
scrollTitleRows:
|
|
704
|
+
scrollTitleRows: isDeepInterviewQuestion ? DEEP_INTERVIEW_SELECTOR_SCROLL_TITLE_ROWS : undefined,
|
|
704
705
|
otherOptionLabel: shouldNumberOptions
|
|
705
706
|
? formatNumberedOptionLabel(OTHER_OPTION, optionLabels.length)
|
|
706
707
|
: undefined,
|