@gajae-code/coding-agent 0.8.2 → 0.9.0
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 +29 -0
- package/dist/types/append-only-mode.d.ts +31 -0
- package/dist/types/capability/rule.d.ts +8 -0
- package/dist/types/cli/fixture-report.d.ts +20 -0
- package/dist/types/cli/fixture-report.test.d.ts +1 -0
- package/dist/types/cli/nofile-limit.d.ts +11 -0
- package/dist/types/cli/update-cli.d.ts +22 -0
- package/dist/types/config/settings-schema.d.ts +105 -1
- package/dist/types/discovery/index.d.ts +1 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/export/ttsr.d.ts +14 -3
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +5 -2
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +7 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +3 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +26 -0
- package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +42 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +119 -13
- package/dist/types/goals/runtime.d.ts +1 -0
- package/dist/types/hooks/native-skill-hook.d.ts +12 -0
- package/dist/types/modes/components/assistant-message.d.ts +5 -1
- package/dist/types/modes/components/status-line.d.ts +9 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +5 -0
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +14 -0
- package/dist/types/modes/shared/agent-wire/event-contract.d.ts +19 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +23 -1
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
- package/dist/types/notifications/config.d.ts +6 -0
- package/dist/types/notifications/telegram-daemon.d.ts +8 -0
- package/dist/types/notifications/threaded-render.d.ts +6 -0
- package/dist/types/sdk.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +43 -1
- package/dist/types/session/file-mention-pruning.d.ts +8 -0
- package/dist/types/session/messages.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +17 -1
- package/dist/types/skill-state/active-state.d.ts +18 -1
- package/dist/types/system-prompt.d.ts +18 -2
- package/dist/types/task/executor.d.ts +1 -0
- package/dist/types/task/fork-context-budget.d.ts +7 -0
- package/dist/types/task/token-log.d.ts +30 -0
- package/dist/types/task/token-log.test.d.ts +1 -0
- package/dist/types/tools/bisect.d.ts +81 -0
- package/dist/types/tools/index.d.ts +1 -0
- package/dist/types/tools/search.d.ts +2 -2
- package/dist/types/utils/file-mentions.d.ts +14 -0
- package/dist/types/utils/git.d.ts +32 -0
- package/dist/types/web/scrapers/youtube.d.ts +8 -0
- package/package.json +7 -7
- package/src/append-only-mode.ts +58 -0
- package/src/autoresearch/prompt.md +1 -1
- package/src/autoresearch/tools/log-experiment.ts +1 -1
- package/src/capability/rule.ts +9 -0
- package/src/cli/config-cli.ts +27 -3
- package/src/cli/fixture-report.test.ts +230 -0
- package/src/cli/fixture-report.ts +171 -0
- package/src/cli/nofile-limit.ts +48 -0
- package/src/cli/update-cli.ts +85 -9
- package/src/cli.ts +26 -0
- package/src/commands/contribution-prep.ts +1 -1
- package/src/config/settings-schema.ts +68 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +12 -13
- package/src/defaults/gjc/skills/ralplan/SKILL.md +30 -93
- package/src/defaults/gjc/skills/team/SKILL.md +16 -13
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +59 -4
- package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +30 -0
- package/src/defaults/gjc-defaults.ts +9 -0
- package/src/discovery/helpers.ts +10 -0
- package/src/discovery/index.ts +1 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +60 -2
- package/src/export/ttsr.ts +96 -18
- package/src/extensibility/extensions/runner.ts +181 -219
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +156 -44
- package/src/gjc-runtime/deep-interview-state.ts +31 -2
- package/src/gjc-runtime/session-state-sidecar.ts +208 -13
- package/src/gjc-runtime/state-writer.ts +15 -0
- package/src/gjc-runtime/team-runtime.ts +164 -15
- package/src/gjc-runtime/ultragoal-guard.ts +55 -66
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +315 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +1593 -143
- package/src/gjc-runtime/workflow-manifest.generated.json +113 -2
- package/src/gjc-runtime/workflow-manifest.ts +74 -1
- package/src/goals/runtime.ts +10 -2
- package/src/hindsight/backend.ts +1 -3
- package/src/hooks/native-skill-hook.ts +95 -4
- package/src/internal-urls/docs-index.generated.ts +14 -5
- package/src/main.ts +43 -2
- package/src/memories/index.ts +12 -1
- package/src/modes/components/assistant-message.ts +241 -82
- package/src/modes/components/custom-editor.ts +1 -0
- package/src/modes/components/status-line.ts +167 -54
- package/src/modes/components/tool-execution.ts +44 -19
- package/src/modes/controllers/command-controller.ts +39 -2
- package/src/modes/controllers/event-controller.ts +66 -47
- package/src/modes/controllers/input-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +9 -1
- package/src/modes/rpc/rpc-mode.ts +29 -3
- package/src/modes/rpc/rpc-types.ts +9 -0
- package/src/modes/shared/agent-wire/command-validation.ts +4 -0
- package/src/modes/shared/agent-wire/event-contract.ts +22 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +82 -0
- package/src/modes/shared/agent-wire/scopes.ts +1 -0
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +17 -4
- package/src/notifications/config.ts +16 -4
- package/src/notifications/index.ts +78 -5
- package/src/notifications/telegram-daemon-cli.ts +2 -2
- package/src/notifications/telegram-daemon-control.ts +2 -2
- package/src/notifications/telegram-daemon.ts +85 -8
- package/src/notifications/threaded-render.ts +17 -1
- package/src/prompts/agents/architect.md +19 -10
- package/src/prompts/agents/critic.md +16 -18
- package/src/prompts/agents/executor.md +14 -11
- package/src/prompts/agents/planner.md +18 -13
- package/src/prompts/ci-green-request.md +3 -8
- package/src/prompts/goals/goal-continuation.md +0 -5
- package/src/prompts/goals/goal-mode-active.md +0 -5
- package/src/prompts/system/agent-creation-architect.md +2 -2
- package/src/prompts/system/plan-mode-active.md +1 -1
- package/src/prompts/system/plan-mode-subagent.md +3 -3
- package/src/prompts/system/project-prompt.md +0 -13
- package/src/prompts/system/subagent-system-prompt.md +3 -2
- package/src/prompts/system/system-prompt.md +26 -19
- package/src/prompts/system/volatile-project-context.md +10 -0
- package/src/prompts/system/web-search.md +4 -7
- package/src/prompts/tools/apply-patch.md +6 -0
- package/src/prompts/tools/ast-edit.md +1 -0
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/bisect.md +22 -0
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/cron.md +0 -2
- package/src/prompts/tools/eval.md +2 -2
- package/src/prompts/tools/find.md +1 -1
- package/src/prompts/tools/github.md +6 -6
- package/src/prompts/tools/goal.md +1 -3
- package/src/prompts/tools/image-gen.md +12 -5
- package/src/prompts/tools/irc.md +5 -4
- package/src/prompts/tools/job.md +2 -2
- package/src/prompts/tools/lsp.md +1 -1
- package/src/prompts/tools/monitor.md +2 -2
- package/src/prompts/tools/patch.md +2 -2
- package/src/prompts/tools/replace.md +2 -21
- package/src/prompts/tools/rewind.md +1 -1
- package/src/prompts/tools/search-tool-bm25.md +1 -11
- package/src/prompts/tools/search.md +1 -1
- package/src/prompts/tools/skill.md +3 -2
- package/src/prompts/tools/ssh.md +2 -1
- package/src/prompts/tools/subagent.md +2 -2
- package/src/prompts/tools/task.md +4 -5
- package/src/prompts/tools/todo-write.md +1 -1
- package/src/prompts/tools/vim.md +1 -0
- package/src/prompts/tools/web-search.md +2 -0
- package/src/prompts/tools/write.md +8 -2
- package/src/sdk.ts +32 -22
- package/src/session/agent-session.ts +732 -107
- package/src/session/file-mention-pruning.ts +64 -0
- package/src/session/messages.ts +4 -0
- package/src/session/session-manager.ts +59 -2
- package/src/skill-state/active-state.ts +141 -5
- package/src/skill-state/deep-interview-mutation-guard.ts +67 -3
- package/src/slash-commands/builtin-registry.ts +65 -0
- package/src/system-prompt.ts +57 -16
- package/src/task/executor.ts +125 -20
- package/src/task/fork-context-advisory.ts +2 -7
- package/src/task/fork-context-budget.ts +9 -0
- package/src/task/index.ts +4 -3
- package/src/task/output-manager.ts +4 -2
- package/src/task/token-log.test.ts +188 -0
- package/src/task/token-log.ts +179 -0
- package/src/tools/bisect.ts +367 -0
- package/src/tools/index.ts +3 -0
- package/src/tools/output-meta.ts +116 -4
- package/src/tools/search.ts +8 -2
- package/src/tools/skill.ts +15 -0
- package/src/utils/file-mentions.ts +39 -7
- package/src/utils/git.ts +73 -0
- package/src/web/scrapers/youtube.ts +24 -12
|
@@ -33,8 +33,8 @@ import { registerAskAnswerSource } from "../tools/ask-answer-registry";
|
|
|
33
33
|
import { registerTelegramFileSink } from "./attachment-registry";
|
|
34
34
|
import {
|
|
35
35
|
getNotificationConfig,
|
|
36
|
-
isGloballyConfigured,
|
|
37
36
|
isSessionNotificationsEnabled,
|
|
37
|
+
isTelegramConfigured,
|
|
38
38
|
type NotificationConfig,
|
|
39
39
|
sessionTag,
|
|
40
40
|
} from "./config";
|
|
@@ -350,6 +350,12 @@ interface SessionRuntime {
|
|
|
350
350
|
/** Assistant text already flushed before an ask this turn (turn-scoped dedupe
|
|
351
351
|
* so turn_end does not re-emit the pre-ask lead-in). Reset each turn. */
|
|
352
352
|
preAskFlushedText?: string;
|
|
353
|
+
/** Live streaming: opt-in flag, monotonic per-turn ref, and emit throttle state. */
|
|
354
|
+
stream: boolean;
|
|
355
|
+
turnSeq?: number;
|
|
356
|
+
liveRef?: string;
|
|
357
|
+
lastLiveAt?: number;
|
|
358
|
+
lastLiveText?: string;
|
|
353
359
|
/** Cancels the postmortem cleanup that emits `session_closed` on process teardown. */
|
|
354
360
|
cancelPostmortemCleanup: () => void;
|
|
355
361
|
}
|
|
@@ -383,6 +389,33 @@ export function notificationsEnabled(): boolean {
|
|
|
383
389
|
return process.env.GJC_NOTIFICATIONS === "1" || Boolean(process.env.GJC_NOTIFICATIONS_TOKEN);
|
|
384
390
|
}
|
|
385
391
|
|
|
392
|
+
// Live streaming (opt-in): emit throttled non-finalized `turn_stream` frames as
|
|
393
|
+
// the assistant message streams so remote clients can edit ONE message live. The
|
|
394
|
+
// finalized frame (turn_end) carries the same messageRef and stays authoritative,
|
|
395
|
+
// so a dropped live frame self-heals. Off unless GJC_NOTIFICATIONS_STREAM=1.
|
|
396
|
+
function streamingEnabled(): boolean {
|
|
397
|
+
return process.env.GJC_NOTIFICATIONS_STREAM === "1";
|
|
398
|
+
}
|
|
399
|
+
function streamIntervalMs(): number {
|
|
400
|
+
return Math.max(200, Number(process.env.GJC_NOTIFICATIONS_STREAM_INTERVAL_MS) || 500);
|
|
401
|
+
}
|
|
402
|
+
// Max chars of a turn's assistant text carried by the FINALIZED turn_stream (and
|
|
403
|
+
// the pre-ask capture). Default 3500 keeps the mirror a glanceable per-turn
|
|
404
|
+
// summary; a client that splits long messages (the Telegram daemon does so via
|
|
405
|
+
// splitTelegramHtml, scheduling each chunk through the shared rate-limit pool so
|
|
406
|
+
// the fan-out never bypasses the per-chat limit) can raise it with
|
|
407
|
+
// GJC_NOTIFICATIONS_TURN_MAX to deliver full turns. The value is clamped to a
|
|
408
|
+
// finite [280, TURN_TEXT_MAX_CEILING] range: a non-finite or non-positive env
|
|
409
|
+
// (unset, NaN, Infinity, <= 0) falls back to the default, so the cap can never
|
|
410
|
+
// be unbounded. Live frames are intentionally NOT raised — they stay one
|
|
411
|
+
// editable preview message rather than fanning a long in-progress turn across
|
|
412
|
+
// sends.
|
|
413
|
+
const TURN_TEXT_MAX_CEILING = 40_000;
|
|
414
|
+
function turnTextMax(): number {
|
|
415
|
+
const raw = Number(process.env.GJC_NOTIFICATIONS_TURN_MAX);
|
|
416
|
+
if (!Number.isFinite(raw) || raw <= 0) return 3500;
|
|
417
|
+
return Math.min(TURN_TEXT_MAX_CEILING, Math.max(280, raw));
|
|
418
|
+
}
|
|
386
419
|
function resolveSettings(settingsOverride?: Settings): ResolvedSettings {
|
|
387
420
|
if (settingsOverride)
|
|
388
421
|
return { settings: settingsOverride, cfg: getNotificationConfig(settingsOverride), settingsAvailable: true };
|
|
@@ -708,6 +741,7 @@ export function createNotificationsExtension(api: ExtensionAPI, options: { setti
|
|
|
708
741
|
cancelPostmortemCleanup: () => {},
|
|
709
742
|
redact,
|
|
710
743
|
verbosity,
|
|
744
|
+
stream: streamingEnabled(),
|
|
711
745
|
sessionTag: tag,
|
|
712
746
|
busy: false,
|
|
713
747
|
pendingInbound: new Set<number>(),
|
|
@@ -724,7 +758,7 @@ export function createNotificationsExtension(api: ExtensionAPI, options: { setti
|
|
|
724
758
|
});
|
|
725
759
|
logger.info(`notifications: serving session ${id} at ${endpoint.url} (unattended=${unattended})`);
|
|
726
760
|
|
|
727
|
-
if (settingsAvailable && settings &&
|
|
761
|
+
if (settingsAvailable && settings && isTelegramConfigured(cfg)) {
|
|
728
762
|
try {
|
|
729
763
|
await ensureTelegramDaemonRunning({ settings, cwd: ctx.cwd, sessionId: id });
|
|
730
764
|
} catch (e) {
|
|
@@ -1063,7 +1097,15 @@ export function createNotificationsExtension(api: ExtensionAPI, options: { setti
|
|
|
1063
1097
|
if (!text || text === rt.preAskFlushedText) return;
|
|
1064
1098
|
rt.preAskFlushedText = text;
|
|
1065
1099
|
try {
|
|
1066
|
-
rt.server.pushFrame(
|
|
1100
|
+
rt.server.pushFrame(
|
|
1101
|
+
JSON.stringify({
|
|
1102
|
+
type: "turn_stream",
|
|
1103
|
+
sessionId: id,
|
|
1104
|
+
phase: "finalized",
|
|
1105
|
+
text,
|
|
1106
|
+
...(rt.liveRef ? { messageRef: rt.liveRef } : {}),
|
|
1107
|
+
}),
|
|
1108
|
+
);
|
|
1067
1109
|
} catch (e) {
|
|
1068
1110
|
logger.warn(`notifications: pushFrame (turn) failed: ${String(e)}`);
|
|
1069
1111
|
}
|
|
@@ -1087,12 +1129,43 @@ export function createNotificationsExtension(api: ExtensionAPI, options: { setti
|
|
|
1087
1129
|
const id = sessionId(ctx);
|
|
1088
1130
|
const rt = runtimes.get(id);
|
|
1089
1131
|
if (!rt) return;
|
|
1090
|
-
const text = rt.redact ? undefined : summaryFromMessage(event.message,
|
|
1132
|
+
const text = rt.redact ? undefined : summaryFromMessage(event.message, turnTextMax());
|
|
1091
1133
|
if (text) flushTurnText(rt, id, text);
|
|
1092
1134
|
// Reset per-turn streaming state so the next turn starts fresh and a later
|
|
1093
1135
|
// turn with identical text is not falsely deduped.
|
|
1094
1136
|
rt.currentTurnText = undefined;
|
|
1095
1137
|
rt.preAskFlushedText = undefined;
|
|
1138
|
+
rt.liveRef = undefined;
|
|
1139
|
+
rt.lastLiveAt = undefined;
|
|
1140
|
+
rt.lastLiveText = undefined;
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
// Live streaming (opt-in): push throttled in-progress assistant text as
|
|
1144
|
+
// non-finalized turn_stream frames so remote clients edit one message as the
|
|
1145
|
+
// turn streams. The finalized frame (turn_end) carries the same messageRef and
|
|
1146
|
+
// lands the authoritative text. Suppressed under redaction.
|
|
1147
|
+
api.on("message_update", (event, ctx) => {
|
|
1148
|
+
const id = sessionId(ctx);
|
|
1149
|
+
const rt = runtimes.get(id);
|
|
1150
|
+
if (!rt?.stream || rt.redact) return;
|
|
1151
|
+
if ((event.message as { role?: unknown }).role !== "assistant") return;
|
|
1152
|
+
if (rt.liveRef === undefined) {
|
|
1153
|
+
rt.turnSeq = (rt.turnSeq ?? 0) + 1;
|
|
1154
|
+
rt.liveRef = String(rt.turnSeq);
|
|
1155
|
+
}
|
|
1156
|
+
const now = Date.now();
|
|
1157
|
+
if (now - (rt.lastLiveAt ?? 0) < streamIntervalMs()) return;
|
|
1158
|
+
const text = summaryFromMessage(event.message, 3500);
|
|
1159
|
+
if (!text || text === rt.lastLiveText) return;
|
|
1160
|
+
rt.lastLiveAt = now;
|
|
1161
|
+
rt.lastLiveText = text;
|
|
1162
|
+
try {
|
|
1163
|
+
rt.server.pushFrame(
|
|
1164
|
+
JSON.stringify({ type: "turn_stream", sessionId: id, phase: "live", text, messageRef: rt.liveRef }),
|
|
1165
|
+
);
|
|
1166
|
+
} catch (e) {
|
|
1167
|
+
logger.warn(`notifications: pushFrame (live) failed: ${String(e)}`);
|
|
1168
|
+
}
|
|
1096
1169
|
});
|
|
1097
1170
|
|
|
1098
1171
|
// Stream agent-produced images (computer/browser/tool screenshots) as
|
|
@@ -1106,7 +1179,7 @@ export function createNotificationsExtension(api: ExtensionAPI, options: { setti
|
|
|
1106
1179
|
// flush can emit it before the ask prompt. Role-scoped: message_end also
|
|
1107
1180
|
// fires for the user prompt, which must never be mirrored back as turn output.
|
|
1108
1181
|
if ((event.message as { role?: unknown }).role === "assistant") {
|
|
1109
|
-
const turnText = summaryFromMessage(event.message,
|
|
1182
|
+
const turnText = summaryFromMessage(event.message, turnTextMax());
|
|
1110
1183
|
if (turnText) rt.currentTurnText = turnText;
|
|
1111
1184
|
}
|
|
1112
1185
|
for (const img of imageAttachmentsFromMessage(event.message)) {
|
|
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { YAML } from "bun";
|
|
4
4
|
import type { Settings } from "../config/settings";
|
|
5
|
-
import { getNotificationConfig,
|
|
5
|
+
import { getNotificationConfig, isTelegramConfigured } from "./config";
|
|
6
6
|
import { daemonPaths } from "./daemon-paths";
|
|
7
7
|
import type { TelegramDaemonOptions } from "./telegram-daemon";
|
|
8
8
|
|
|
@@ -146,7 +146,7 @@ export async function runDaemonInternal(argv: string[], deps: RunDaemonInternalD
|
|
|
146
146
|
const resolvedAgentDir = agentDir ?? process.env.GJC_CODING_AGENT_DIR ?? path.join(process.cwd(), ".gjc", "agent");
|
|
147
147
|
const settings = await resolveDaemonSettings(resolvedAgentDir, deps);
|
|
148
148
|
const cfg = getNotificationConfig(settings as Settings);
|
|
149
|
-
if (!
|
|
149
|
+
if (!isTelegramConfigured(cfg)) return;
|
|
150
150
|
const { clearTelegramControlRequest, readTelegramControlRequest } = await import("./telegram-daemon-control");
|
|
151
151
|
const Daemon: TelegramDaemonConstructor =
|
|
152
152
|
deps.DaemonImpl ?? (await import("./telegram-daemon")).TelegramNotificationDaemon;
|
|
@@ -20,7 +20,7 @@ import type {
|
|
|
20
20
|
DaemonStatus,
|
|
21
21
|
} from "../daemon/control-types";
|
|
22
22
|
import { resolveGjcRuntimeSpawnInfo } from "../daemon/runtime";
|
|
23
|
-
import { getNotificationConfig,
|
|
23
|
+
import { getNotificationConfig, isTelegramConfigured, tokenFingerprint } from "./config";
|
|
24
24
|
import {
|
|
25
25
|
daemonPaths,
|
|
26
26
|
isFreshLiveOwner,
|
|
@@ -159,7 +159,7 @@ export class TelegramDaemonController implements BuiltInDaemonController {
|
|
|
159
159
|
async status(): Promise<DaemonStatus> {
|
|
160
160
|
const runtime = this.runtimeInfo();
|
|
161
161
|
const cfg = getNotificationConfig(this.settings);
|
|
162
|
-
const configured =
|
|
162
|
+
const configured = isTelegramConfigured(cfg);
|
|
163
163
|
if (!configured) {
|
|
164
164
|
return { kind: this.kind, configured: false, health: "not_configured", runtime };
|
|
165
165
|
}
|
|
@@ -8,7 +8,7 @@ import { withFileLock } from "../config/file-lock";
|
|
|
8
8
|
import type { Settings } from "../config/settings";
|
|
9
9
|
import type { DaemonRuntimeInfo } from "../daemon/control-types";
|
|
10
10
|
import { resolveGjcRuntimeSpawnInfo } from "../daemon/runtime";
|
|
11
|
-
import { getNotificationConfig,
|
|
11
|
+
import { getNotificationConfig, isTelegramConfigured, tokenFingerprint } from "./config";
|
|
12
12
|
import { parseInThreadConfigCommand } from "./config-commands";
|
|
13
13
|
import { daemonPaths } from "./daemon-paths";
|
|
14
14
|
import {
|
|
@@ -636,7 +636,7 @@ export async function ensureTelegramDaemonRunning(
|
|
|
636
636
|
deps: TelegramDaemonDeps = {},
|
|
637
637
|
): Promise<EnsureDaemonResult> {
|
|
638
638
|
const cfg = getNotificationConfig(input.settings);
|
|
639
|
-
if (!
|
|
639
|
+
if (!isTelegramConfigured(cfg)) return "disabled";
|
|
640
640
|
const root = notificationRootForCwd(input.cwd);
|
|
641
641
|
const fp = tokenFingerprint(cfg.botToken);
|
|
642
642
|
const spawned = await spawnTelegramDaemonOwner(
|
|
@@ -869,6 +869,8 @@ interface PendingThreadedFrame {
|
|
|
869
869
|
export class TelegramNotificationDaemon {
|
|
870
870
|
readonly aliasTable: AliasTable;
|
|
871
871
|
readonly messageRoutes = new Map<string | number, CallbackRoute | Omit<CallbackRoute, "answer">>();
|
|
872
|
+
/** Telegram message id backing each streamed `${sessionId}:${coalesceKey}`, for in-place edits. */
|
|
873
|
+
private readonly liveMessages = new Map<string, number>();
|
|
872
874
|
readonly sessions = new Map<string, SessionSocket>();
|
|
873
875
|
private readonly runtime: NotificationOperatorRuntime;
|
|
874
876
|
private readonly sessionRouter: OperatorEventRouter<SessionSocket>;
|
|
@@ -1666,6 +1668,9 @@ export class TelegramNotificationDaemon {
|
|
|
1666
1668
|
})) as { ok?: boolean };
|
|
1667
1669
|
if (res?.ok === false) return;
|
|
1668
1670
|
this.topics.delete(sessionId);
|
|
1671
|
+
for (const k of [...this.liveMessages.keys()]) {
|
|
1672
|
+
if (k.startsWith(`${sessionId}:`)) this.liveMessages.delete(k);
|
|
1673
|
+
}
|
|
1669
1674
|
this.topicOwnerByIdentity.forEach((ownerSessionId, identityKey) => {
|
|
1670
1675
|
if (ownerSessionId === sessionId) this.topicOwnerByIdentity.delete(identityKey);
|
|
1671
1676
|
});
|
|
@@ -1795,11 +1800,25 @@ export class TelegramNotificationDaemon {
|
|
|
1795
1800
|
|
|
1796
1801
|
/** Drain the shared rate-limit pool and deliver each granted send to its topic. */
|
|
1797
1802
|
private async flushPool(): Promise<void> {
|
|
1798
|
-
|
|
1803
|
+
const batch = this.pool.drain();
|
|
1804
|
+
// Within a batch a finalized frame supersedes any still-queued live frame for
|
|
1805
|
+
// the same streamed message (finalized outranks live), so drop the stale live
|
|
1806
|
+
// edit — otherwise the authoritative final text could be overwritten by an
|
|
1807
|
+
// older partial delivered right after it.
|
|
1808
|
+
const finalizedKeys = new Set<string>();
|
|
1809
|
+
for (const item of batch) {
|
|
1810
|
+
if (item.lane === "finalized" && item.coalesceKey !== undefined) {
|
|
1811
|
+
finalizedKeys.add(`${item.sessionId}:${item.coalesceKey}`);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
for (const item of batch) {
|
|
1799
1815
|
const { send, topicId } = item.payload;
|
|
1800
1816
|
if (topicId && !(await this.pairedChatIsPrivate())) continue;
|
|
1801
1817
|
// Threaded topic when available; otherwise deliver flat to the paired chat.
|
|
1802
1818
|
const threadField = topicId ? { message_thread_id: Number(topicId) } : {};
|
|
1819
|
+
const ckey = send.editable ? item.coalesceKey : undefined;
|
|
1820
|
+
const editKey = ckey !== undefined ? `${item.sessionId}:${ckey}` : undefined;
|
|
1821
|
+
if (item.lane === "live" && editKey && finalizedKeys.has(editKey)) continue;
|
|
1803
1822
|
try {
|
|
1804
1823
|
if (send.method === "sendPhoto" && send.photoBase64) {
|
|
1805
1824
|
// Real photo upload (the default botApi multiparts base64 -> file).
|
|
@@ -1822,21 +1841,79 @@ export class TelegramNotificationDaemon {
|
|
|
1822
1841
|
parse_mode: TELEGRAM_PARSE_MODE,
|
|
1823
1842
|
});
|
|
1824
1843
|
} else if (send.text) {
|
|
1825
|
-
|
|
1826
|
-
|
|
1844
|
+
const chunks = splitTelegramHtml(send.text);
|
|
1845
|
+
const existingId = editKey ? this.liveMessages.get(editKey) : undefined;
|
|
1846
|
+
if (editKey && existingId !== undefined && chunks.length === 1) {
|
|
1847
|
+
// In-place edit of the streamed message. An unchanged edit is
|
|
1848
|
+
// rejected by Telegram ("message is not modified") and swallowed.
|
|
1849
|
+
await this.botApi.call("editMessageText", {
|
|
1827
1850
|
chat_id: this.opts.chatId,
|
|
1828
|
-
|
|
1829
|
-
text,
|
|
1851
|
+
message_id: existingId,
|
|
1852
|
+
text: chunks[0],
|
|
1830
1853
|
parse_mode: TELEGRAM_PARSE_MODE,
|
|
1831
1854
|
});
|
|
1855
|
+
} else {
|
|
1856
|
+
// A single granted slot MUST map to a single Telegram send. When the
|
|
1857
|
+
// rendered text splits into multiple chunks (e.g. a long finalized
|
|
1858
|
+
// turn raised via GJC_NOTIFICATIONS_TURN_MAX), deliver the first
|
|
1859
|
+
// chunk on this token and re-submit the remaining chunks as their
|
|
1860
|
+
// own pool items so each consumes a token on a later drain.
|
|
1861
|
+
// Otherwise one frame would fan out into many sends against a single
|
|
1862
|
+
// slot, bypassing the per-chat rate-limit / fairness invariant.
|
|
1863
|
+
const res = (await this.botApi.call("sendMessage", {
|
|
1864
|
+
chat_id: this.opts.chatId,
|
|
1865
|
+
...threadField,
|
|
1866
|
+
text: chunks[0]!,
|
|
1867
|
+
parse_mode: TELEGRAM_PARSE_MODE,
|
|
1868
|
+
})) as { result?: { message_id?: number } };
|
|
1869
|
+
for (let i = 1; i < chunks.length; i++) {
|
|
1870
|
+
// Continuation chunks are fresh, non-editable text sends: no
|
|
1871
|
+
// coalesce key (they neither replace nor are replaced by other
|
|
1872
|
+
// frames) and no media payload.
|
|
1873
|
+
this.pool.submit({
|
|
1874
|
+
sessionId: item.sessionId,
|
|
1875
|
+
lane: item.lane,
|
|
1876
|
+
payload: {
|
|
1877
|
+
send: {
|
|
1878
|
+
...send,
|
|
1879
|
+
method: "sendMessage",
|
|
1880
|
+
text: chunks[i]!,
|
|
1881
|
+
editable: false,
|
|
1882
|
+
coalesceKey: undefined,
|
|
1883
|
+
photoBase64: undefined,
|
|
1884
|
+
documentBase64: undefined,
|
|
1885
|
+
},
|
|
1886
|
+
topicId,
|
|
1887
|
+
},
|
|
1888
|
+
});
|
|
1889
|
+
}
|
|
1890
|
+
const firstMessageId = res?.result?.message_id;
|
|
1891
|
+
if (editKey && ckey !== undefined && firstMessageId !== undefined) {
|
|
1892
|
+
this.recordLiveMessage(item.sessionId, ckey, firstMessageId);
|
|
1893
|
+
}
|
|
1832
1894
|
}
|
|
1833
1895
|
}
|
|
1834
1896
|
} catch {
|
|
1835
|
-
// Best-effort: a failed send must never stop the daemon.
|
|
1897
|
+
// Best-effort: a failed send/edit must never stop the daemon.
|
|
1836
1898
|
}
|
|
1837
1899
|
}
|
|
1838
1900
|
}
|
|
1839
1901
|
|
|
1902
|
+
/**
|
|
1903
|
+
* Track the Telegram message id backing a streamed `(sessionId, coalesceKey)`
|
|
1904
|
+
* so later live/finalized frames edit it in place. Evicts this session's stale
|
|
1905
|
+
* same-category entries (e.g. prior turns) so the map stays bounded.
|
|
1906
|
+
*/
|
|
1907
|
+
private recordLiveMessage(sessionId: string, coalesceKey: string, messageId: number): void {
|
|
1908
|
+
const mapKey = `${sessionId}:${coalesceKey}`;
|
|
1909
|
+
const category = coalesceKey.split(":")[0] ?? "";
|
|
1910
|
+
const prefix = `${sessionId}:${category}:`;
|
|
1911
|
+
for (const k of [...this.liveMessages.keys()]) {
|
|
1912
|
+
if (k !== mapKey && k.startsWith(prefix)) this.liveMessages.delete(k);
|
|
1913
|
+
}
|
|
1914
|
+
this.liveMessages.set(mapKey, messageId);
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1840
1917
|
/**
|
|
1841
1918
|
* Threaded Mode is unavailable (the bot owner has not enabled forum topics in
|
|
1842
1919
|
* @BotFather, so `createForumTopic` fails). Deliver the rendered frame flat to
|
|
@@ -32,6 +32,12 @@ export interface ThreadedSend {
|
|
|
32
32
|
coalesceKey?: string;
|
|
33
33
|
/** True for the one-time identity header (the daemon pins it once). */
|
|
34
34
|
identity?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* When true the daemon may deliver this as an in-place edit of the message
|
|
37
|
+
* previously sent under the same `(sessionId, coalesceKey)` instead of a new
|
|
38
|
+
* message. Set for streamed turn frames so live + finalized share one message.
|
|
39
|
+
*/
|
|
40
|
+
editable?: boolean;
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
interface ThreadedFrame {
|
|
@@ -135,11 +141,21 @@ export function renderThreadedFrame(frame: ThreadedFrame): ThreadedSend | undefi
|
|
|
135
141
|
if (!raw) return undefined;
|
|
136
142
|
const text = markdownToTelegramHtml(raw);
|
|
137
143
|
const finalized = frame.phase === "finalized";
|
|
144
|
+
// A per-turn ref ties the streamed live edits and the finalized text to
|
|
145
|
+
// ONE message. Without a ref (streaming off), finalized keeps its legacy
|
|
146
|
+
// keyless behaviour: a fresh message per turn.
|
|
147
|
+
const ref = str(frame.messageRef);
|
|
148
|
+
const coalesceKey = finalized
|
|
149
|
+
? ref
|
|
150
|
+
? `turn:${ref}`
|
|
151
|
+
: undefined
|
|
152
|
+
: `turn:${ref ?? str(frame.sessionId) ?? ""}`;
|
|
138
153
|
return {
|
|
139
154
|
method: "sendMessage",
|
|
140
155
|
lane: finalized ? "finalized" : "live",
|
|
141
156
|
text,
|
|
142
|
-
coalesceKey
|
|
157
|
+
coalesceKey,
|
|
158
|
+
editable: coalesceKey !== undefined,
|
|
143
159
|
};
|
|
144
160
|
}
|
|
145
161
|
case "image_attachment": {
|
|
@@ -19,18 +19,20 @@ You may receive a forked parent-conversation snapshot as background. Your read-o
|
|
|
19
19
|
- Assess architecture, boundaries, interfaces, tradeoffs, and long-horizon maintainability.
|
|
20
20
|
- Verify spec compliance before style concerns.
|
|
21
21
|
- Review security, correctness, performance, and code quality with severity-rated feedback.
|
|
22
|
+
- Provide the strongest fair antithesis to risky plans, then synthesize a better path when possible.
|
|
23
|
+
- Broaden thin plans with missed architectural sub-scope, viable options, and concrete design constraints.
|
|
22
24
|
- Surface an architectural status: `CLEAR`, `WATCH`, or `BLOCK`.
|
|
23
25
|
- Surface a code-review recommendation: `APPROVE`, `COMMENT`, or `REQUEST CHANGES`.
|
|
24
26
|
</goals>
|
|
25
27
|
|
|
26
28
|
<constraints>
|
|
27
29
|
- Read-only: never write, edit, format, commit, push, or mutate files.
|
|
28
|
-
- Exception: you may use
|
|
30
|
+
- Exception: you may use restricted `bash` only for sanctioned GJC workflow CLI persistence (`gjc ralplan --write ...`) and GJC workflow state read/write/contract commands (`gjc state ...`). For `gjc ralplan --write`, pass review markdown through `GJC_RALPLAN_ARTIFACT` and `--artifact-env GJC_RALPLAN_ARTIFACT`, not as a file path. Do not use bash for product-source writes, direct handoffs, state clears, or general shell work.
|
|
29
31
|
- Never approve code or plans you have not grounded in inspected files.
|
|
30
32
|
- Never give generic advice detached from this codebase.
|
|
31
33
|
- Never approve CRITICAL or HIGH severity issues.
|
|
32
34
|
- Do not skip spec compliance to jump to style nitpicks.
|
|
33
|
-
- Be constructive: explain why an issue matters and how to fix it.
|
|
35
|
+
- Be constructive: explain why an issue matters and how to fix it or strengthen the design.
|
|
34
36
|
</constraints>
|
|
35
37
|
|
|
36
38
|
<review_stages>
|
|
@@ -38,9 +40,10 @@ You may receive a forked parent-conversation snapshot as background. Your read-o
|
|
|
38
40
|
2. Gather file-backed evidence.
|
|
39
41
|
3. Stage 1 — Spec compliance: does the implementation or plan solve the requested problem without missing or extra behavior?
|
|
40
42
|
4. Stage 2 — Architecture: boundaries, coupling, data flow, failure modes, maintainability, and tradeoffs.
|
|
41
|
-
5. Stage 3 —
|
|
42
|
-
6.
|
|
43
|
-
7.
|
|
43
|
+
5. Stage 3 — Constructive synthesis: where the plan is thin, add options, constraints, or design shape that would make it stronger.
|
|
44
|
+
6. Stage 4 — Code quality/security/performance: only after spec compliance and root-cause checks.
|
|
45
|
+
7. Rate each issue by severity: CRITICAL, HIGH, MEDIUM, LOW.
|
|
46
|
+
8. Return architectural status and code-review recommendation.
|
|
44
47
|
</review_stages>
|
|
45
48
|
|
|
46
49
|
<root_cause_fallback_policy>
|
|
@@ -53,7 +56,8 @@ A narrow compatibility fallback can be acceptable only when it is scoped to a kn
|
|
|
53
56
|
- Important claims cite concrete files or inspected evidence.
|
|
54
57
|
- Root cause is identified when reviewing a defect.
|
|
55
58
|
- Recommendations are concrete and implementable.
|
|
56
|
-
- Tradeoffs are acknowledged.
|
|
59
|
+
- Tradeoffs and antithesis are acknowledged without becoming adversarial-only.
|
|
60
|
+
- Thin plans receive constructive synthesis or broadening when useful.
|
|
57
61
|
- Issues include severity and fix suggestions.
|
|
58
62
|
- Architectural Status is one of `CLEAR`, `WATCH`, or `BLOCK`.
|
|
59
63
|
- Code Review Recommendation is one of `APPROVE`, `COMMENT`, or `REQUEST CHANGES`.
|
|
@@ -67,7 +71,7 @@ A narrow compatibility fallback can be acceptable only when it is scoped to a kn
|
|
|
67
71
|
Evidence-backed claims being reviewed or introduced.
|
|
68
72
|
|
|
69
73
|
## Analysis
|
|
70
|
-
Evidence-backed findings.
|
|
74
|
+
Evidence-backed findings, antithesis, and constructive synthesis.
|
|
71
75
|
|
|
72
76
|
## Root Cause
|
|
73
77
|
Fundamental issue, if applicable.
|
|
@@ -76,7 +80,7 @@ Fundamental issue, if applicable.
|
|
|
76
80
|
For each issue: severity, file/reference, impact, fix suggestion.
|
|
77
81
|
|
|
78
82
|
## Recommendations
|
|
79
|
-
Prioritized concrete actions.
|
|
83
|
+
Prioritized concrete actions, including additive design options for thin plans.
|
|
80
84
|
|
|
81
85
|
## Architectural Status
|
|
82
86
|
`CLEAR` / `WATCH` / `BLOCK`
|
|
@@ -87,9 +91,14 @@ Prioritized concrete actions.
|
|
|
87
91
|
## Tradeoffs
|
|
88
92
|
Table or bullets comparing viable options when relevant.
|
|
89
93
|
|
|
90
|
-
|
|
94
|
+
Structured findings:
|
|
95
|
+
- Report every issue through `report_finding` as you confirm it, mapping severity CRITICAL → P0, HIGH → P1, MEDIUM → P2, LOW → P3. The Findings section summarizes what you reported; `report_finding` is the structured channel the caller's pipeline consumes.
|
|
96
|
+
|
|
97
|
+
Persistence (ralplan runs only):
|
|
98
|
+
- Only when your assignment is part of an active ralplan run (the assignment references a ralplan stage or `stage_n`), persist the full review through the restricted bash CLI:
|
|
91
99
|
|
|
92
100
|
gjc ralplan --write --stage architect --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json
|
|
93
101
|
|
|
94
|
-
|
|
102
|
+
Use the assignment-provided `stage_n`; if a duplicate-write error occurs, retry with the incremented N. Then return ONLY the write receipt (`run_id`, `path`, `sha256`, `stage`, `stage_n`) plus compact verdict (Architectural Status + Code Review Recommendation) in `yield.result.data`. Never paste the full review body back; the caller reads the persisted artifact when needed.
|
|
103
|
+
- Otherwise (any non-ralplan invocation), do NOT call `gjc ralplan --write`; return the full review in `yield.result.data` instead.
|
|
95
104
|
</output_contract>
|
|
@@ -12,32 +12,34 @@ You are Critic. Decide whether a work plan is actionable before execution begins
|
|
|
12
12
|
</identity>
|
|
13
13
|
|
|
14
14
|
<goal>
|
|
15
|
-
Review plan clarity, completeness, verification, big-picture fit, referenced files, and representative implementation paths. Return OKAY when executors can proceed without guessing; return
|
|
15
|
+
Review plan clarity, completeness, verification, big-picture fit, referenced files, and representative implementation paths. Return OKAY when executors can proceed without guessing; return ITERATE or REJECT with concrete fixes when they cannot. A valid ITERATE reason is “spec too thin here — expand” with specific enrichment requests, not only defect findings.
|
|
16
16
|
</goal>
|
|
17
17
|
|
|
18
18
|
<constraints>
|
|
19
19
|
- Read-only: do not write, edit, format, commit, push, or mutate files.
|
|
20
|
-
- Exception: you may use
|
|
20
|
+
- Exception: you may use restricted `bash` only for sanctioned GJC workflow CLI persistence (`gjc ralplan --write ...`) and GJC workflow state read/write/contract commands (`gjc state ...`). For `gjc ralplan --write`, pass evaluation markdown through `GJC_RALPLAN_ARTIFACT` and `--artifact-env GJC_RALPLAN_ARTIFACT`, not as a file path. Do not use bash for product-source writes, direct handoffs, state clears, or general shell work.
|
|
21
21
|
- A lone file path is valid input; read and evaluate it.
|
|
22
22
|
- Reject YAML-only plans as invalid plan format when a human-readable plan is required.
|
|
23
23
|
- Do not invent problems; report no issues found when the plan passes.
|
|
24
|
-
- Escalate routing needs upward: planner for plan revision,
|
|
25
|
-
- For consensus planning, reject shallow alternatives, driver contradictions, vague risks, weak verification,
|
|
24
|
+
- Escalate routing needs upward: planner for plan revision, the deep-interview skill for requirements gathering, architect for code analysis.
|
|
25
|
+
- For consensus planning, reject shallow alternatives, driver contradictions, vague risks, weak verification, missing acceptance criteria, or under-specified areas needing expansion before execution.
|
|
26
26
|
</constraints>
|
|
27
27
|
|
|
28
28
|
<execution_loop>
|
|
29
29
|
1. Read the plan and referenced artifacts.
|
|
30
30
|
2. Extract and verify file references.
|
|
31
|
-
3. Evaluate clarity, verifiability, completeness,
|
|
31
|
+
3. Evaluate clarity, verifiability, completeness, big-picture fit, and principle/option consistency.
|
|
32
32
|
4. Simulate two or three representative implementation tasks against actual files.
|
|
33
|
-
5.
|
|
33
|
+
5. Distinguish fatal defects from thin areas that need additive detail.
|
|
34
|
+
6. Issue OKAY, ITERATE, or REJECT with specific evidence and required changes.
|
|
34
35
|
</execution_loop>
|
|
35
36
|
|
|
36
37
|
<success_criteria>
|
|
37
38
|
- Every referenced file that matters is verified or called out as unverified.
|
|
38
39
|
- Representative tasks have been mentally simulated.
|
|
39
40
|
- Verdict is clear: OKAY, ITERATE, or REJECT.
|
|
40
|
-
-
|
|
41
|
+
- ITERATE may request concrete expansion: assumptions, acceptance criteria, options, missed sub-scope, or verification detail.
|
|
42
|
+
- Rejections list top critical improvements with actionable wording.
|
|
41
43
|
- Certainty is differentiated: definitely missing versus possibly unclear.
|
|
42
44
|
</success_criteria>
|
|
43
45
|
|
|
@@ -49,26 +51,22 @@ Review plan clarity, completeness, verification, big-picture fit, referenced fil
|
|
|
49
51
|
Concise evidence-backed explanation of verified claims.
|
|
50
52
|
|
|
51
53
|
## Missing Evidence
|
|
52
|
-
Definitely missing
|
|
54
|
+
Definitely missing, unverified evidence, or thin areas needing expansion; otherwise `None`.
|
|
53
55
|
|
|
54
56
|
## Approval Boundary
|
|
55
57
|
What execution may proceed with, and what remains outside approval.
|
|
56
58
|
|
|
57
59
|
## Summary
|
|
58
|
-
- Clarity
|
|
59
|
-
- Verifiability
|
|
60
|
-
- Completeness
|
|
61
|
-
- Big Picture
|
|
62
|
-
- Principle/Option Consistency
|
|
63
|
-
- Alternatives Depth
|
|
64
|
-
- Risk/Verification Rigor
|
|
60
|
+
- Clarity; Verifiability; Completeness; Big Picture; Principle/Option Consistency; Alternatives Depth; Risk/Verification Rigor
|
|
65
61
|
|
|
66
62
|
## Required Changes
|
|
67
|
-
If not OKAY, list concrete
|
|
63
|
+
If not OKAY, list concrete defect fixes or expansion requirements; otherwise write `None`.
|
|
68
64
|
|
|
69
|
-
|
|
65
|
+
Persistence (ralplan runs only):
|
|
66
|
+
- Only when your assignment is part of an active ralplan run (the assignment references a ralplan stage or `stage_n`), persist the full evaluation through the restricted bash CLI:
|
|
70
67
|
|
|
71
68
|
gjc ralplan --write --stage critic --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json
|
|
72
69
|
|
|
73
|
-
|
|
70
|
+
Use the assignment-provided `stage_n`; if a duplicate-write error occurs, retry with the incremented N. Then return ONLY the write receipt (`run_id`, `path`, `sha256`, `stage`, `stage_n`) plus compact verdict (OKAY / ITERATE / REJECT) in `yield.result.data`. Never paste the full evaluation body back; the caller reads the persisted artifact when needed.
|
|
71
|
+
- Otherwise (any non-ralplan invocation), do NOT call `gjc ralplan --write`; return the full evaluation in `yield.result.data` instead.
|
|
74
72
|
</output_contract>
|
|
@@ -35,25 +35,28 @@ Explore just enough context, implement the smallest correct change, and leave co
|
|
|
35
35
|
This mode activates only when the assignment explicitly labels Executor as Ultragoal completion QA/red-team or asks for `executorQa` red-team evidence. Otherwise, preserve ordinary Executor behavior.
|
|
36
36
|
|
|
37
37
|
When active:
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
- Try to break the work with adversarial cases, not just happy-path confirmations.
|
|
44
|
-
- Report the QA matrix with the final field names `executorQa.contractCoverage`, `executorQa.surfaceEvidence`, `executorQa.adversarialCases`, and `executorQa.artifactRefs`.
|
|
45
|
-
- Include artifact refs for every executed surface and adversarial case: transcript ids, log paths, screenshots, image verdicts, CLI replay records, PTY captures, test outputs, or other durable evidence.
|
|
46
|
-
- Use `status: "not_applicable"` only for rows in `executorQa.contractCoverage` and `executorQa.surfaceEvidence`; each not-applicable row requires `contractRef` plus `reason`. `executorQa.adversarialCases` rows cannot be not-applicable.
|
|
47
|
-
- Report blockers for any missing plan/spec/acceptance source, contract ambiguity, plan/code mismatch, untestable surface, failed adversarial case, shallow evidence, or missing artifact ref.
|
|
38
|
+
- Follow the exact `executorQa` contract provided in the assignment (matrix shape, row fields, artifact/replay rules); the runtime validates it strictly. If the assignment omits the contract, read the ultragoal SKILL's executor QA section before producing evidence.
|
|
39
|
+
- Start from the approved plan/spec/acceptance criteria, then user-facing contracts; treat plan/code mismatches as blockers.
|
|
40
|
+
- Exercise the real user-facing invocation and try adversarial cases, not only happy paths. `inlineEvidence` is supplemental only and never sole proof for live surfaces.
|
|
41
|
+
- Do not call `ask`; record unresolved decisions with `gjc ultragoal record-review-blockers`.
|
|
42
|
+
- Report blockers for missing plan/spec/acceptance source, contract ambiguity, plan/code mismatch, untestable surface, failed adversarial case, shallow evidence, or missing artifact refs.
|
|
48
43
|
</ultragoal_red_team_mode>
|
|
49
44
|
|
|
50
45
|
<success_criteria>
|
|
51
46
|
- Requested behavior is implemented in the assigned scope.
|
|
52
47
|
- Modified files match existing style and contracts.
|
|
53
48
|
- No temporary/debug leftovers remain.
|
|
54
|
-
- Final output lists changed files, important decisions, and verification performed or intentionally left to the parent.
|
|
55
49
|
</success_criteria>
|
|
56
50
|
|
|
51
|
+
<output_contract>
|
|
52
|
+
Yield with `result.data` containing:
|
|
53
|
+
- `changed_files`: paths touched, with one-line purpose each
|
|
54
|
+
- `decisions`: important implementation decisions and assumptions
|
|
55
|
+
- `verification`: checks performed, or precise verification left to the parent
|
|
56
|
+
- `blockers`: unresolved blockers with attempted fixes; empty when none
|
|
57
|
+
- In ultragoal red-team mode, `result.data` instead carries the `executorQa` matrix with its exact camelCase field names (`contractCoverage`, `surfaceEvidence`, `adversarialCases`, `artifactRefs`); the runtime validates those names verbatim — do not rename them to snake_case.
|
|
58
|
+
</output_contract>
|
|
59
|
+
|
|
57
60
|
<failure_recovery>
|
|
58
61
|
Try another approach, split the blocker smaller, and re-check repo evidence before escalating. After materially different failed approaches, stop adding risk and report the blocker with attempted fixes.
|
|
59
62
|
</failure_recovery>
|
|
@@ -12,38 +12,35 @@ You are Planner. Turn requests into actionable work plans. You plan; you do not
|
|
|
12
12
|
</identity>
|
|
13
13
|
|
|
14
14
|
<goal>
|
|
15
|
-
Leave execution with a right-sized, evidence-grounded plan: scope, steps, acceptance criteria, risks, verification, and handoff guidance.
|
|
15
|
+
Leave execution with a right-sized, evidence-grounded plan: scope, steps, acceptance criteria, risks, verification, and handoff guidance. When input is thin, enrich it: identify underspecified areas, propose assumptions/options, surface missed sub-scope, and add testable acceptance details instead of merely sequencing what was stated.
|
|
16
16
|
</goal>
|
|
17
17
|
|
|
18
18
|
<constraints>
|
|
19
19
|
- Read-only: never write, edit, format, commit, push, or mutate files.
|
|
20
|
-
- Exception: you may use
|
|
21
|
-
- Persist durable plans only through `gjc ralplan --write
|
|
20
|
+
- Exception: you may use restricted `bash` only for sanctioned GJC workflow CLI persistence (`gjc ralplan --write ...`) and GJC workflow state read/write/contract commands (`gjc state ...`). For `gjc ralplan --write`, pass plan markdown through `GJC_RALPLAN_ARTIFACT` and `--artifact-env GJC_RALPLAN_ARTIFACT`, not as a file path. Do not use bash for product-source writes, direct handoffs, state clears, or general shell work.
|
|
21
|
+
- Persist durable plans only through `gjc ralplan --write`; never write plan files to `/tmp`, the repository, or any other path.
|
|
22
22
|
- Inspect the repository before asking about code facts.
|
|
23
|
-
- Ask only about priorities, tradeoffs, scope decisions, timelines, or preferences
|
|
24
|
-
- Right-size the step count
|
|
23
|
+
- Ask only about priorities, tradeoffs, scope decisions, timelines, or preferences repository inspection cannot resolve. When running headless (no user available to ask), do not block on questions — record the assumption and open question in the plan's Decision Drivers / Risks instead.
|
|
24
|
+
- Right-size the step count; do not default to a fixed number of steps.
|
|
25
25
|
- Do not redesign architecture unless the task requires it.
|
|
26
26
|
- Use GJC command/path semantics (`gjc`, `.gjc`) for product-facing guidance.
|
|
27
27
|
</constraints>
|
|
28
28
|
|
|
29
29
|
<execution_loop>
|
|
30
|
-
|
|
31
|
-
2. Classify the task as simple, refactor, feature, or broad initiative.
|
|
32
|
-
3. Identify affected resources, constraints, and dependencies.
|
|
33
|
-
4. Ask one preference/priority question only when a real branch remains.
|
|
34
|
-
5. Draft an adaptive plan with acceptance criteria, verification, risks, and handoff.
|
|
30
|
+
Inspect relevant files, classify the task, identify resources/constraints/dependencies/missing detail/enrichments, ask one question only for a real unresolved branch (or record it as an explicit assumption when headless), then draft an adaptive plan with acceptance criteria, verification, risks, options, and handoff.
|
|
35
31
|
</execution_loop>
|
|
36
32
|
|
|
37
33
|
<success_criteria>
|
|
38
34
|
- Plan has scope-matched actionable steps.
|
|
39
35
|
- Acceptance criteria are specific and testable.
|
|
40
36
|
- Codebase facts are backed by inspected files.
|
|
37
|
+
- Thin specs are expanded with explicit assumptions, additive options, missed sub-scope, and verification detail.
|
|
41
38
|
- Risks and verification commands are concrete.
|
|
42
39
|
- Handoff identifies when to use executor, architect, critic, team, or ultragoal.
|
|
43
40
|
</success_criteria>
|
|
44
41
|
|
|
45
42
|
<output_contract>
|
|
46
|
-
Build
|
|
43
|
+
Build one markdown plan containing:
|
|
47
44
|
- Summary
|
|
48
45
|
- Intent Diff
|
|
49
46
|
- Decision Drivers
|
|
@@ -57,9 +54,17 @@ Build the full plan as a single markdown document containing:
|
|
|
57
54
|
- Verification Plan
|
|
58
55
|
- Risks and mitigations
|
|
59
56
|
|
|
60
|
-
|
|
57
|
+
Default durable workflow output:
|
|
58
|
+
- Persist the markdown through the restricted bash CLI, passing the plan through `GJC_RALPLAN_ARTIFACT` and `--artifact-env GJC_RALPLAN_ARTIFACT` (never a file path, never `/tmp`):
|
|
61
59
|
|
|
62
60
|
gjc ralplan --write --stage planner --stage_n <N> --artifact-env GJC_RALPLAN_ARTIFACT --json
|
|
61
|
+
Use the assignment-provided `stage_n`; if a duplicate-write error occurs, retry with the incremented N.
|
|
63
62
|
|
|
64
|
-
Then return
|
|
63
|
+
- Then return ONLY the write receipt (`run_id`, `path`, `sha256`, `stage`, `stage_n`) plus a compact plan summary (<=10 lines). Never paste the full plan body back; the caller reads the persisted artifact when needed.
|
|
64
|
+
|
|
65
|
+
Inline-output exception:
|
|
66
|
+
- If the assignment explicitly disables persistence (for example, "do not persist", "read-only: do not mutate `.gjc/`", or "leader persists it"), do NOT use `gjc ralplan --write`.
|
|
67
|
+
- In that case, put the complete markdown document itself inside `yield.result.data.plan_markdown`.
|
|
68
|
+
- If the assignment asks to show or return the complete plan body but does not explicitly disable persistence, keep the durable workflow output path and include any requested body alongside the receipt in `yield.result.data`; do not skip the Planner stage artifact.
|
|
69
|
+
- Never return a pointer such as "see message body", "returned inline", or "leader persists"; subagent plain text is not the result channel, and the caller only receives `yield.result.data`.
|
|
65
70
|
</output_contract>
|