@gajae-code/coding-agent 0.7.8 → 0.7.9
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 +18 -0
- package/README.md +6 -0
- package/dist/types/config/keybindings.d.ts +1 -1
- package/dist/types/config/model-profile-activation.d.ts +19 -2
- package/dist/types/config/model-profiles.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/settings-schema.d.ts +2 -2
- package/dist/types/gjc-runtime/launch-tmux.d.ts +12 -3
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +3 -1
- package/dist/types/modes/components/hook-input.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +9 -0
- package/dist/types/modes/components/status-line.d.ts +3 -0
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +3 -1
- package/dist/types/modes/interactive-mode.d.ts +3 -1
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +21 -1
- package/dist/types/modes/types.d.ts +3 -1
- package/dist/types/notifications/daemon-paths.d.ts +9 -0
- package/dist/types/notifications/telegram-daemon-cli.d.ts +8 -2
- package/dist/types/notifications/telegram-daemon.d.ts +1 -10
- package/dist/types/tools/ask.d.ts +24 -0
- package/dist/types/tools/ultragoal-ask-guard.d.ts +9 -2
- package/package.json +7 -7
- package/src/cli/notify-cli.ts +1 -1
- package/src/cli.ts +1 -0
- package/src/config/keybindings.ts +1 -1
- package/src/config/model-profile-activation.ts +92 -11
- package/src/config/model-profiles.ts +15 -2
- package/src/config/model-registry.ts +81 -5
- package/src/config/settings-schema.ts +3 -2
- package/src/coordinator-mcp/server.ts +4 -2
- package/src/deep-interview/render-middleware.ts +1 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +14 -6
- package/src/defaults/gjc/skills/ralplan/SKILL.md +14 -7
- package/src/gjc-runtime/deep-interview-runtime.ts +1 -1
- package/src/gjc-runtime/launch-tmux.ts +303 -46
- package/src/gjc-runtime/team-runtime.ts +69 -4
- package/src/gjc-runtime/ultragoal-guard.ts +11 -5
- package/src/internal-urls/docs-index.generated.ts +6 -6
- package/src/modes/components/custom-model-preset-wizard.ts +2 -4
- package/src/modes/components/hook-editor.ts +7 -4
- package/src/modes/components/hook-input.ts +4 -0
- package/src/modes/components/model-selector.ts +84 -13
- package/src/modes/components/settings-selector.ts +36 -21
- package/src/modes/components/status-line/segments.ts +10 -2
- package/src/modes/components/status-line.ts +1 -0
- package/src/modes/controllers/extension-ui-controller.ts +2 -0
- package/src/modes/controllers/selector-controller.ts +130 -7
- package/src/modes/interactive-mode.ts +7 -2
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +11 -4
- package/src/modes/types.ts +6 -1
- package/src/notifications/daemon-paths.ts +22 -0
- package/src/notifications/telegram-daemon-cli.ts +13 -4
- package/src/notifications/telegram-daemon.ts +2 -21
- package/src/prompts/system/system-prompt.md +5 -0
- package/src/sdk.ts +10 -1
- package/src/session/agent-session.ts +10 -1
- package/src/tools/ask.ts +11 -1
- package/src/tools/eval.ts +2 -2
- package/src/tools/render-utils.ts +2 -0
- package/src/tools/ultragoal-ask-guard.ts +39 -5
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* the interactive select/editor UI) when an unattended controller + gate broker
|
|
12
12
|
* are attached is wired with the transport in #321 and exercised by #323.
|
|
13
13
|
*/
|
|
14
|
-
import type { RpcJsonSchema } from "../../rpc/rpc-types";
|
|
14
|
+
import type { RpcJsonSchema, RpcWorkflowGateKind, RpcWorkflowStage } from "../../rpc/rpc-types";
|
|
15
15
|
import type { OpenGateInput } from "./workflow-gate-broker";
|
|
16
16
|
|
|
17
17
|
/** "Other (type your own)" sentinel, mirroring the interactive ask tool. */
|
|
@@ -26,6 +26,11 @@ export interface AskGateDeepInterviewState {
|
|
|
26
26
|
ambiguity: number;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
export interface AskGateWorkflowGateMeta {
|
|
30
|
+
stage: RpcWorkflowStage;
|
|
31
|
+
kind: RpcWorkflowGateKind;
|
|
32
|
+
}
|
|
33
|
+
|
|
29
34
|
export interface AskGateQuestion {
|
|
30
35
|
id: string;
|
|
31
36
|
question: string;
|
|
@@ -37,6 +42,8 @@ export interface AskGateQuestion {
|
|
|
37
42
|
* `stage_state`; when absent, the question text is regex-parsed as a fallback.
|
|
38
43
|
*/
|
|
39
44
|
deepInterview?: AskGateDeepInterviewState;
|
|
45
|
+
/** Override the emitted workflow gate address for non-deep-interview ask prompts. */
|
|
46
|
+
workflowGate?: AskGateWorkflowGateMeta;
|
|
40
47
|
}
|
|
41
48
|
|
|
42
49
|
export interface AskGateResult {
|
|
@@ -157,13 +164,13 @@ function structuredDeepInterviewState(meta: AskGateDeepInterviewState): Record<s
|
|
|
157
164
|
return state;
|
|
158
165
|
}
|
|
159
166
|
|
|
160
|
-
/** Build the `workflow_gate` open-input for one
|
|
167
|
+
/** Build the `workflow_gate` open-input for one ask question. */
|
|
161
168
|
export function questionToGate(question: AskGateQuestion): OpenGateInput {
|
|
162
169
|
const labels = question.options.map(o => o.label);
|
|
163
170
|
const schema = questionAnswerSchema(question, labels);
|
|
164
171
|
return {
|
|
165
|
-
stage: "deep-interview",
|
|
166
|
-
kind: "question",
|
|
172
|
+
stage: question.workflowGate?.stage ?? "deep-interview",
|
|
173
|
+
kind: question.workflowGate?.kind ?? "question",
|
|
167
174
|
schema,
|
|
168
175
|
options: question.options.map((o, i) => ({
|
|
169
176
|
value: o.label,
|
package/src/modes/types.ts
CHANGED
|
@@ -290,7 +290,12 @@ export interface InteractiveModeContext {
|
|
|
290
290
|
dialogOptions?: ExtensionUIDialogOptions,
|
|
291
291
|
): Promise<string | undefined>;
|
|
292
292
|
hideHookSelector(): void;
|
|
293
|
-
showHookInput(
|
|
293
|
+
showHookInput(
|
|
294
|
+
title: string,
|
|
295
|
+
placeholder?: string,
|
|
296
|
+
dialogOptions?: ExtensionUIDialogOptions,
|
|
297
|
+
inputOptions?: { readonly initialValue?: string },
|
|
298
|
+
): Promise<string | undefined>;
|
|
294
299
|
hideHookInput(): void;
|
|
295
300
|
showHookEditor(
|
|
296
301
|
title: string,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
|
|
3
|
+
export interface DaemonPaths {
|
|
4
|
+
dir: string;
|
|
5
|
+
lock: string;
|
|
6
|
+
state: string;
|
|
7
|
+
roots: string;
|
|
8
|
+
steal: string;
|
|
9
|
+
aliases: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function daemonPaths(agentDir: string): DaemonPaths {
|
|
13
|
+
const dir = path.join(agentDir, "notifications");
|
|
14
|
+
return {
|
|
15
|
+
dir,
|
|
16
|
+
lock: path.join(dir, "telegram-daemon.lock"),
|
|
17
|
+
state: path.join(dir, "telegram-daemon.state.json"),
|
|
18
|
+
roots: path.join(dir, "telegram-daemon.roots.json"),
|
|
19
|
+
steal: path.join(dir, "telegram-daemon.steal"),
|
|
20
|
+
aliases: path.join(dir, "telegram-callback-aliases.json"),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -3,12 +3,19 @@ import * as path from "node:path";
|
|
|
3
3
|
import { YAML } from "bun";
|
|
4
4
|
import type { Settings } from "../config/settings";
|
|
5
5
|
import { getNotificationConfig, isGloballyConfigured } from "./config";
|
|
6
|
-
import { daemonPaths
|
|
7
|
-
import {
|
|
6
|
+
import { daemonPaths } from "./daemon-paths";
|
|
7
|
+
import type { TelegramDaemonOptions } from "./telegram-daemon";
|
|
8
|
+
|
|
9
|
+
type TelegramDaemonRunner = {
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
requestStop(reason?: "reload" | "signal" | "stop"): void;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type TelegramDaemonConstructor = new (opts: TelegramDaemonOptions) => TelegramDaemonRunner;
|
|
8
15
|
|
|
9
16
|
export interface RunDaemonInternalDeps {
|
|
10
17
|
SettingsImpl?: { init: (options?: { agentDir?: string }) => Promise<Pick<Settings, "get" | "getAgentDir">> };
|
|
11
|
-
DaemonImpl?:
|
|
18
|
+
DaemonImpl?: TelegramDaemonConstructor;
|
|
12
19
|
processPid?: number;
|
|
13
20
|
pidAlive?: (pid: number) => boolean;
|
|
14
21
|
}
|
|
@@ -140,7 +147,9 @@ export async function runDaemonInternal(argv: string[], deps: RunDaemonInternalD
|
|
|
140
147
|
const settings = await resolveDaemonSettings(resolvedAgentDir, deps);
|
|
141
148
|
const cfg = getNotificationConfig(settings as Settings);
|
|
142
149
|
if (!isGloballyConfigured(cfg) || !cfg.botToken || !cfg.chatId) return;
|
|
143
|
-
const
|
|
150
|
+
const { clearTelegramControlRequest, readTelegramControlRequest } = await import("./telegram-daemon-control");
|
|
151
|
+
const Daemon: TelegramDaemonConstructor =
|
|
152
|
+
deps.DaemonImpl ?? (await import("./telegram-daemon")).TelegramNotificationDaemon;
|
|
144
153
|
const daemon = new Daemon({
|
|
145
154
|
settings: settings as Settings,
|
|
146
155
|
ownerId,
|
|
@@ -10,6 +10,7 @@ import type { DaemonRuntimeInfo } from "../daemon/control-types";
|
|
|
10
10
|
import { resolveGjcRuntimeSpawnInfo } from "../daemon/runtime";
|
|
11
11
|
import { getNotificationConfig, isGloballyConfigured, tokenFingerprint } from "./config";
|
|
12
12
|
import { parseInThreadConfigCommand } from "./config-commands";
|
|
13
|
+
import { daemonPaths } from "./daemon-paths";
|
|
13
14
|
import { buildCompactChoiceGrid, TELEGRAM_PARSE_MODE } from "./html-format";
|
|
14
15
|
import type {
|
|
15
16
|
SessionCloseTarget,
|
|
@@ -62,16 +63,6 @@ export interface DaemonState {
|
|
|
62
63
|
version: 1;
|
|
63
64
|
stoppedAt?: number;
|
|
64
65
|
}
|
|
65
|
-
|
|
66
|
-
export interface DaemonPaths {
|
|
67
|
-
dir: string;
|
|
68
|
-
lock: string;
|
|
69
|
-
state: string;
|
|
70
|
-
roots: string;
|
|
71
|
-
steal: string;
|
|
72
|
-
aliases: string;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
66
|
export interface TelegramDaemonFs {
|
|
76
67
|
mkdir(path: string, opts?: fs.MakeDirectoryOptions): Promise<void>;
|
|
77
68
|
readFile(path: string, encoding: BufferEncoding): Promise<string>;
|
|
@@ -184,17 +175,7 @@ async function fetchWithRetry(
|
|
|
184
175
|
throw lastErr;
|
|
185
176
|
}
|
|
186
177
|
|
|
187
|
-
export
|
|
188
|
-
const dir = path.join(agentDir, "notifications");
|
|
189
|
-
return {
|
|
190
|
-
dir,
|
|
191
|
-
lock: path.join(dir, "telegram-daemon.lock"),
|
|
192
|
-
state: path.join(dir, "telegram-daemon.state.json"),
|
|
193
|
-
roots: path.join(dir, "telegram-daemon.roots.json"),
|
|
194
|
-
steal: path.join(dir, "telegram-daemon.steal"),
|
|
195
|
-
aliases: path.join(dir, "telegram-callback-aliases.json"),
|
|
196
|
-
};
|
|
197
|
-
}
|
|
178
|
+
export { type DaemonPaths, daemonPaths } from "./daemon-paths";
|
|
198
179
|
|
|
199
180
|
/**
|
|
200
181
|
* Attach session-lifecycle control (create/close/resume) to the running daemon.
|
|
@@ -82,6 +82,11 @@ Use for read-only plan critique. It approves only when execution can proceed wit
|
|
|
82
82
|
- Do not load or inject user-home Anthropic model or provider instructions (`~/.anthropic-model`, `~/.openai-code`) into the model context.
|
|
83
83
|
- Public commands, paths, examples, and workflow names must use `gjc` and `.gjc`.
|
|
84
84
|
</runtime-state>
|
|
85
|
+
<self-awareness>
|
|
86
|
+
- When the user asks about GJC usage, how to use a GJC feature/command/workflow, or about the gajae-code system itself, do not answer from memory alone. First clone the canonical source repository into `/tmp` (e.g. `git clone --depth 1 https://github.com/Yeachan-Heo/gajae-code /tmp/gajae-code-<unique>`), then read and analyze the actual source there to ground your answer.
|
|
87
|
+
- Reuse an existing fresh clone under `/tmp` instead of re-cloning when one is already present in the session.
|
|
88
|
+
- Base usage and system answers on what the cloned source actually says; cite concrete files/paths from the clone rather than guessing.
|
|
89
|
+
</self-awareness>
|
|
85
90
|
</gjc-runtime>
|
|
86
91
|
|
|
87
92
|
<communication>
|
package/src/sdk.ts
CHANGED
|
@@ -2007,7 +2007,16 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2007
2007
|
.map(name => toolRegistry.get(name))
|
|
2008
2008
|
.filter((tool): tool is AgentTool => tool !== undefined)
|
|
2009
2009
|
// AgentSession tool wrapping is not installed until after Agent construction.
|
|
2010
|
-
.map(tool =>
|
|
2010
|
+
.map(tool =>
|
|
2011
|
+
guardToolForUltragoalAsk(
|
|
2012
|
+
tool,
|
|
2013
|
+
() => sessionManager.getCwd(),
|
|
2014
|
+
() => ({
|
|
2015
|
+
activeSkillState: session?.getActiveSkillState(),
|
|
2016
|
+
sessionId: sessionManager.getSessionId?.() ?? null,
|
|
2017
|
+
}),
|
|
2018
|
+
),
|
|
2019
|
+
);
|
|
2011
2020
|
|
|
2012
2021
|
const openaiWebsocketSetting = settings.get("providers.openaiWebsockets") ?? "off";
|
|
2013
2022
|
const preferOpenAICodexWebsockets =
|
|
@@ -3920,7 +3920,16 @@ export class AgentSession {
|
|
|
3920
3920
|
|
|
3921
3921
|
#prepareToolForExecution<T extends AgentTool>(tool: T): T {
|
|
3922
3922
|
return this.#wrapToolForDeepInterviewMutationGuard(
|
|
3923
|
-
this.#wrapToolForAcpPermission(
|
|
3923
|
+
this.#wrapToolForAcpPermission(
|
|
3924
|
+
guardToolForUltragoalAsk(
|
|
3925
|
+
tool,
|
|
3926
|
+
() => this.sessionManager.getCwd(),
|
|
3927
|
+
() => ({
|
|
3928
|
+
activeSkillState: this.getActiveSkillState(),
|
|
3929
|
+
sessionId: this.sessionManager.getSessionId(),
|
|
3930
|
+
}),
|
|
3931
|
+
),
|
|
3932
|
+
),
|
|
3924
3933
|
);
|
|
3925
3934
|
}
|
|
3926
3935
|
|
package/src/tools/ask.ts
CHANGED
|
@@ -62,6 +62,11 @@ const DeepInterviewMeta = z.object({
|
|
|
62
62
|
ambiguity: z.number().min(0).max(1).describe("ambiguity at ask time (0..1)"),
|
|
63
63
|
});
|
|
64
64
|
|
|
65
|
+
const WorkflowGateMeta = z.object({
|
|
66
|
+
stage: z.enum(["deep-interview", "ralplan", "ultragoal"]).describe("workflow gate stage"),
|
|
67
|
+
kind: z.enum(["question", "approval", "execution"]).describe("workflow gate kind"),
|
|
68
|
+
});
|
|
69
|
+
|
|
65
70
|
const QuestionItem = z.object({
|
|
66
71
|
id: z.string().describe("question id"),
|
|
67
72
|
question: z.string().describe("question text"),
|
|
@@ -69,6 +74,7 @@ const QuestionItem = z.object({
|
|
|
69
74
|
multi: z.boolean().describe("allow multiple selections").optional(),
|
|
70
75
|
recommended: z.number().describe("recommended option index").optional(),
|
|
71
76
|
deepInterview: DeepInterviewMeta.describe("optional deep-interview round metadata").optional(),
|
|
77
|
+
workflowGate: WorkflowGateMeta.describe("optional workflow gate stage/kind override").optional(),
|
|
72
78
|
});
|
|
73
79
|
|
|
74
80
|
export const askSchema = z.object({
|
|
@@ -551,7 +557,10 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
551
557
|
_onUpdate?: AgentToolUpdateCallback<AskToolDetails>,
|
|
552
558
|
context?: AgentToolContext,
|
|
553
559
|
): Promise<AgentToolResult<AskToolDetails>> {
|
|
554
|
-
await assertUltragoalAskAllowed(this.session.cwd
|
|
560
|
+
await assertUltragoalAskAllowed(this.session.cwd, {
|
|
561
|
+
activeSkillState: this.session.getActiveSkillState?.(),
|
|
562
|
+
sessionId: this.session.getSessionId?.() ?? null,
|
|
563
|
+
});
|
|
555
564
|
const gateEmitter = this.session.getWorkflowGateEmitter?.();
|
|
556
565
|
const canUseWorkflowGate = gateEmitter?.isUnattended() === true;
|
|
557
566
|
|
|
@@ -665,6 +674,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
665
674
|
multi: q.multi,
|
|
666
675
|
recommended: q.recommended,
|
|
667
676
|
deepInterview: q.deepInterview,
|
|
677
|
+
workflowGate: q.workflowGate,
|
|
668
678
|
};
|
|
669
679
|
const answer = await gateEmitter.emitGate(questionToGate(gateQuestion));
|
|
670
680
|
const decoded = gateAnswerToResult(gateQuestion, answer);
|
package/src/tools/eval.ts
CHANGED
|
@@ -754,8 +754,8 @@ function formatStatusEventExpanded(event: EvalStatusEvent, theme: Theme): string
|
|
|
754
754
|
case "git_log":
|
|
755
755
|
if (data.entries) {
|
|
756
756
|
addItems(data.entries as unknown[], e => {
|
|
757
|
-
const entry = e as { sha
|
|
758
|
-
return `${entry.sha} ${truncateToWidth(entry.subject, 50)}`;
|
|
757
|
+
const entry = e as { sha?: string; subject?: string };
|
|
758
|
+
return `${String(entry.sha ?? "")} ${truncateToWidth(String(entry.subject ?? ""), 50)}`;
|
|
759
759
|
});
|
|
760
760
|
}
|
|
761
761
|
break;
|
|
@@ -86,6 +86,7 @@ export const EXPAND_HINT = "(Ctrl+O for more)";
|
|
|
86
86
|
* Get first N lines of text as preview, with each line truncated.
|
|
87
87
|
*/
|
|
88
88
|
export function getPreviewLines(text: string, maxLines: number, maxLineLen: number, ellipsis?: Ellipsis): string[] {
|
|
89
|
+
if (typeof text !== "string") return [];
|
|
89
90
|
const lines = text.split("\n").filter(l => l.trim());
|
|
90
91
|
return lines.slice(0, maxLines).map(l => truncateToWidth(l.trim(), maxLineLen, ellipsis));
|
|
91
92
|
}
|
|
@@ -568,6 +569,7 @@ export function truncateDiffByHunk(
|
|
|
568
569
|
// =============================================================================
|
|
569
570
|
|
|
570
571
|
export function shortenPath(filePath: string, homeDir?: string): string {
|
|
572
|
+
if (typeof filePath !== "string") return "";
|
|
571
573
|
const home = homeDir ?? os.homedir();
|
|
572
574
|
if (home && filePath.startsWith(home)) {
|
|
573
575
|
return `~${filePath.slice(home.length)}`;
|
|
@@ -10,6 +10,29 @@ const ULTRAGOAL_ASK_GUARD = Symbol.for("gajae-code.ultragoalAskGuard");
|
|
|
10
10
|
|
|
11
11
|
type GuardedTool = AgentTool & { [ULTRAGOAL_ASK_GUARD]?: true };
|
|
12
12
|
|
|
13
|
+
export interface UltragoalAskGuardContext {
|
|
14
|
+
activeSkillState?: { skill?: string; session_id?: string } | null;
|
|
15
|
+
sessionId?: string | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const UPSTREAM_PLANNING_ASK_SKILLS = new Set(["deep-interview", "ralplan"]);
|
|
19
|
+
|
|
20
|
+
function normalizedActiveSkill(context?: UltragoalAskGuardContext): string | undefined {
|
|
21
|
+
const skill = context?.activeSkillState?.skill?.trim();
|
|
22
|
+
return skill || undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function sessionScopedAskGuardId(
|
|
26
|
+
context: UltragoalAskGuardContext,
|
|
27
|
+
activeSkill: string | undefined,
|
|
28
|
+
): string | undefined {
|
|
29
|
+
if (activeSkill !== "ultragoal" && !UPSTREAM_PLANNING_ASK_SKILLS.has(activeSkill ?? "")) return undefined;
|
|
30
|
+
const activeSessionId = context.activeSkillState?.session_id?.trim();
|
|
31
|
+
if (activeSessionId) return activeSessionId;
|
|
32
|
+
const sessionId = context.sessionId?.trim();
|
|
33
|
+
return sessionId || undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
13
36
|
export function formatUltragoalAskBlockMessage(diagnostic: UltragoalAskBlockDiagnostic): string {
|
|
14
37
|
return [
|
|
15
38
|
diagnostic.message,
|
|
@@ -18,15 +41,26 @@ export function formatUltragoalAskBlockMessage(diagnostic: UltragoalAskBlockDiag
|
|
|
18
41
|
].join("\n");
|
|
19
42
|
}
|
|
20
43
|
|
|
21
|
-
export async function assertUltragoalAskAllowed(cwd: string): Promise<void> {
|
|
22
|
-
const
|
|
44
|
+
export async function assertUltragoalAskAllowed(cwd: string, context: UltragoalAskGuardContext = {}): Promise<void> {
|
|
45
|
+
const activeSkill = normalizedActiveSkill(context);
|
|
46
|
+
// Deep-interview and ralplan are upstream planning workflows whose core gates
|
|
47
|
+
// are `ask` calls. Scope their Ultragoal check to the current session so stale
|
|
48
|
+
// or ambiguous Ultragoal durable state from another session cannot hijack those
|
|
49
|
+
// prompts; same-session active Ultragoal state still falls through to the
|
|
50
|
+
// blocker/nudge checks below.
|
|
51
|
+
const sessionId = sessionScopedAskGuardId(context, activeSkill);
|
|
52
|
+
const diagnostic = await isUltragoalAskBlocked(cwd, { sessionId });
|
|
23
53
|
if (!diagnostic.active) return;
|
|
24
|
-
const nudge = await consumeUltragoalAskNudge(cwd);
|
|
54
|
+
const nudge = await consumeUltragoalAskNudge(cwd, sessionId);
|
|
25
55
|
if (nudge.nudged) throw new ToolError(nudge.message);
|
|
26
56
|
throw new ToolError(formatUltragoalAskBlockMessage(diagnostic));
|
|
27
57
|
}
|
|
28
58
|
|
|
29
|
-
export function guardToolForUltragoalAsk<T extends AgentTool>(
|
|
59
|
+
export function guardToolForUltragoalAsk<T extends AgentTool>(
|
|
60
|
+
tool: T,
|
|
61
|
+
getCwd: () => string,
|
|
62
|
+
getContext: () => UltragoalAskGuardContext = () => ({}),
|
|
63
|
+
): T {
|
|
30
64
|
if (tool.name !== "ask") return tool;
|
|
31
65
|
const candidate = tool as GuardedTool;
|
|
32
66
|
if (candidate[ULTRAGOAL_ASK_GUARD]) return tool;
|
|
@@ -35,7 +69,7 @@ export function guardToolForUltragoalAsk<T extends AgentTool>(tool: T, getCwd: (
|
|
|
35
69
|
if (prop === ULTRAGOAL_ASK_GUARD) return true;
|
|
36
70
|
if (prop !== "execute") return Reflect.get(target, prop, receiver);
|
|
37
71
|
return async (...args: unknown[]): Promise<unknown> => {
|
|
38
|
-
await assertUltragoalAskAllowed(getCwd());
|
|
72
|
+
await assertUltragoalAskAllowed(getCwd(), getContext());
|
|
39
73
|
return Reflect.apply(target.execute, target, args);
|
|
40
74
|
};
|
|
41
75
|
},
|