@gajae-code/coding-agent 0.5.0 → 0.5.2
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 +36 -0
- package/README.md +1 -1
- package/dist/types/async/job-manager.d.ts +26 -0
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/cli/list-models.d.ts +6 -0
- package/dist/types/cli/setup-cli.d.ts +8 -1
- package/dist/types/commands/gc.d.ts +26 -0
- package/dist/types/commands/setup.d.ts +7 -0
- package/dist/types/config/file-lock-gc.d.ts +5 -0
- package/dist/types/config/file-lock.d.ts +29 -0
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +62 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/defaults/gjc/extensions/grok-build/index.d.ts +1 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/index.d.ts +1 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.d.ts +25 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.d.ts +27 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/billing.d.ts +8 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.d.ts +5 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +10 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/usage.d.ts +2 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.d.ts +2 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/shared/errors.d.ts +38 -0
- package/dist/types/defaults/gjc-grok-cli.d.ts +5 -0
- package/dist/types/extensibility/extensions/index.d.ts +1 -0
- package/dist/types/extensibility/extensions/prefix-command-bridge.d.ts +35 -0
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +103 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +112 -0
- package/dist/types/gjc-runtime/gc-render.d.ts +6 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +134 -0
- package/dist/types/gjc-runtime/ledger-event-renderer.d.ts +68 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +64 -2
- package/dist/types/gjc-runtime/team-gc.d.ts +7 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +5 -0
- package/dist/types/gjc-runtime/tmux-common.d.ts +11 -0
- package/dist/types/gjc-runtime/tmux-gc.d.ts +7 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +13 -0
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +10 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +29 -0
- package/dist/types/harness-control-plane/gc-adapter.d.ts +3 -0
- package/dist/types/harness-control-plane/owner.d.ts +7 -0
- package/dist/types/harness-control-plane/storage.d.ts +20 -0
- package/dist/types/modes/components/hook-selector.d.ts +7 -1
- package/dist/types/modes/components/provider-onboarding-selector.d.ts +1 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +72 -2
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +13 -0
- package/dist/types/modes/shared/agent-wire/session-registry.d.ts +25 -0
- package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +2 -0
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +10 -0
- package/dist/types/modes/theme/defaults/index.d.ts +302 -0
- package/dist/types/modes/theme/theme.d.ts +1 -0
- package/dist/types/modes/types.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +1 -1
- package/dist/types/session/blob-store.d.ts +39 -3
- package/dist/types/session/history-storage.d.ts +2 -2
- package/dist/types/session/session-manager.d.ts +10 -1
- package/dist/types/setup/credential-import.d.ts +79 -0
- package/dist/types/skill-state/workflow-hud.d.ts +14 -0
- package/dist/types/task/executor.d.ts +1 -0
- package/dist/types/task/render.d.ts +1 -1
- package/dist/types/tools/ask.d.ts +15 -1
- package/dist/types/tools/subagent-render.d.ts +7 -1
- package/dist/types/tools/subagent.d.ts +27 -0
- package/dist/types/tools/ultragoal-ask-guard.d.ts +5 -0
- package/dist/types/web/search/index.d.ts +4 -4
- package/dist/types/web/search/provider.d.ts +16 -20
- package/dist/types/web/search/providers/base.d.ts +2 -1
- package/dist/types/web/search/providers/openai-compatible.d.ts +9 -0
- package/dist/types/web/search/types.d.ts +14 -2
- package/package.json +7 -7
- package/scripts/build-binary.ts +7 -0
- package/src/async/job-manager.ts +52 -0
- package/src/cli/args.ts +5 -0
- package/src/cli/auth-broker-cli.ts +1 -0
- package/src/cli/fast-help.ts +2 -0
- package/src/cli/list-models.ts +13 -1
- package/src/cli/setup-cli.ts +138 -3
- package/src/cli.ts +1 -0
- package/src/commands/gc.ts +22 -0
- package/src/commands/harness.ts +7 -3
- package/src/commands/setup.ts +5 -1
- package/src/commands/ultragoal.ts +3 -1
- package/src/config/file-lock-gc.ts +193 -0
- package/src/config/file-lock.ts +66 -10
- package/src/config/model-profile-activation.ts +15 -3
- package/src/config/model-profiles.ts +39 -30
- package/src/config/model-registry.ts +21 -1
- package/src/config/models-config-schema.ts +1 -0
- package/src/config/settings-schema.ts +62 -0
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/server.ts +459 -3
- package/src/defaults/gjc/agent.models.grok-cli.yml +36 -0
- package/src/defaults/gjc/extensions/grok-build/index.ts +1 -0
- package/src/defaults/gjc/extensions/grok-build/package.json +7 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/biome.json +39 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/package.json +8 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/index.ts +1 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +155 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.ts +361 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/billing.ts +57 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +99 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +50 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/usage.ts +56 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +36 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/errors.ts +44 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +131 -113
- package/src/defaults/gjc/skills/deep-interview/lateral-review-panel.md +49 -0
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +30 -8
- package/src/defaults/gjc-defaults.ts +7 -0
- package/src/defaults/gjc-grok-cli.ts +22 -0
- package/src/extensibility/extensions/index.ts +1 -0
- package/src/extensibility/extensions/prefix-command-bridge.ts +128 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +457 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +18 -26
- package/src/gjc-runtime/deep-interview-state.ts +324 -0
- package/src/gjc-runtime/gc-render.ts +70 -0
- package/src/gjc-runtime/gc-runtime.ts +403 -0
- package/src/gjc-runtime/launch-tmux.ts +3 -4
- package/src/gjc-runtime/ledger-event-renderer.ts +164 -0
- package/src/gjc-runtime/ralplan-runtime.ts +232 -19
- package/src/gjc-runtime/state-renderer.ts +12 -3
- package/src/gjc-runtime/state-runtime.ts +48 -30
- package/src/gjc-runtime/state-writer.ts +254 -7
- package/src/gjc-runtime/team-gc.ts +49 -0
- package/src/gjc-runtime/team-runtime.ts +179 -2
- package/src/gjc-runtime/tmux-common.ts +14 -0
- package/src/gjc-runtime/tmux-gc.ts +177 -0
- package/src/gjc-runtime/tmux-sessions.ts +49 -1
- package/src/gjc-runtime/ultragoal-guard.ts +155 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +1239 -31
- package/src/gjc-runtime/workflow-manifest.generated.json +44 -0
- package/src/gjc-runtime/workflow-manifest.ts +12 -0
- package/src/harness-control-plane/gc-adapter.ts +184 -0
- package/src/harness-control-plane/owner.ts +14 -2
- package/src/harness-control-plane/rpc-adapter.ts +1 -1
- package/src/harness-control-plane/storage.ts +70 -0
- package/src/hooks/skill-state.ts +121 -2
- package/src/internal-urls/docs-index.generated.ts +22 -12
- package/src/lsp/defaults.json +1 -0
- package/src/main.ts +18 -3
- package/src/modes/acp/acp-agent.ts +4 -2
- package/src/modes/bridge/bridge-mode.ts +2 -1
- package/src/modes/components/history-search.ts +5 -2
- package/src/modes/components/hook-selector.ts +19 -0
- package/src/modes/components/model-selector.ts +51 -8
- package/src/modes/components/provider-onboarding-selector.ts +6 -1
- package/src/modes/components/status-line/segments.ts +1 -1
- package/src/modes/controllers/command-controller.ts +25 -6
- package/src/modes/controllers/extension-ui-controller.ts +3 -0
- package/src/modes/controllers/selector-controller.ts +81 -1
- package/src/modes/interactive-mode.ts +11 -1
- package/src/modes/rpc/rpc-mode.ts +266 -34
- package/src/modes/shared/agent-wire/command-dispatch.ts +281 -261
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +30 -1
- package/src/modes/shared/agent-wire/host-tool-bridge.ts +3 -0
- package/src/modes/shared/agent-wire/session-registry.ts +109 -0
- package/src/modes/shared/agent-wire/unattended-action-policy.ts +24 -0
- package/src/modes/shared/agent-wire/unattended-run-controller.ts +23 -3
- package/src/modes/shared/agent-wire/unattended-session.ts +32 -2
- package/src/modes/theme/defaults/claude-code.json +100 -0
- package/src/modes/theme/defaults/codex.json +100 -0
- package/src/modes/theme/defaults/index.ts +6 -0
- package/src/modes/theme/defaults/opencode.json +102 -0
- package/src/modes/theme/theme.ts +2 -2
- package/src/modes/types.ts +1 -1
- package/src/prompts/agents/executor.md +5 -2
- package/src/sdk.ts +29 -4
- package/src/session/agent-session.ts +99 -19
- package/src/session/blob-store.ts +59 -3
- package/src/session/history-storage.ts +32 -11
- package/src/session/session-manager.ts +72 -20
- package/src/setup/credential-import.ts +429 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +7 -1
- package/src/skill-state/deep-interview-mutation-guard.ts +2 -1
- package/src/skill-state/workflow-hud.ts +106 -10
- package/src/slash-commands/builtin-registry.ts +3 -2
- package/src/task/executor.ts +16 -1
- package/src/task/render.ts +18 -7
- package/src/tools/ask.ts +59 -2
- package/src/tools/cron.ts +1 -1
- package/src/tools/job.ts +3 -2
- package/src/tools/monitor.ts +36 -1
- package/src/tools/subagent-render.ts +128 -29
- package/src/tools/subagent.ts +173 -9
- package/src/tools/ultragoal-ask-guard.ts +39 -0
- package/src/web/search/index.ts +25 -25
- package/src/web/search/provider.ts +178 -87
- package/src/web/search/providers/base.ts +2 -1
- package/src/web/search/providers/openai-compatible.ts +151 -0
- package/src/web/search/types.ts +47 -22
|
@@ -21,6 +21,8 @@ interface RalplanHudState extends WorkflowGateHudState {
|
|
|
21
21
|
stage?: string;
|
|
22
22
|
waiting?: string;
|
|
23
23
|
iteration?: number;
|
|
24
|
+
iterationFromIndex?: number;
|
|
25
|
+
stages?: string;
|
|
24
26
|
verdict?: string;
|
|
25
27
|
latestSummary?: string;
|
|
26
28
|
pendingApproval?: boolean;
|
|
@@ -100,6 +102,95 @@ export function buildDeepInterviewHudSummary(state: DeepInterviewHudState): Work
|
|
|
100
102
|
};
|
|
101
103
|
}
|
|
102
104
|
|
|
105
|
+
export interface DeepInterviewHudDeriveOptions {
|
|
106
|
+
phase?: string;
|
|
107
|
+
specStatus?: string;
|
|
108
|
+
updatedAt?: string;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function diIsPlainObject(value: unknown): value is Record<string, unknown> {
|
|
112
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function latestScoredAmbiguity(rounds: unknown): number | undefined {
|
|
116
|
+
if (!Array.isArray(rounds)) return undefined;
|
|
117
|
+
for (let index = rounds.length - 1; index >= 0; index--) {
|
|
118
|
+
const round = rounds[index];
|
|
119
|
+
if (diIsPlainObject(round) && round.lifecycle === "scored" && typeof round.ambiguity === "number") {
|
|
120
|
+
return round.ambiguity;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function weakestDimensionFromTopology(
|
|
127
|
+
topology: Record<string, unknown>,
|
|
128
|
+
targetComponent: string | undefined,
|
|
129
|
+
): string | undefined {
|
|
130
|
+
if (!Array.isArray(topology.components)) return undefined;
|
|
131
|
+
const components = topology.components.filter(diIsPlainObject);
|
|
132
|
+
const dimensionOf = (component: Record<string, unknown>): string | undefined =>
|
|
133
|
+
typeof component.weakest_dimension === "string" && component.weakest_dimension.trim()
|
|
134
|
+
? component.weakest_dimension
|
|
135
|
+
: undefined;
|
|
136
|
+
if (targetComponent) {
|
|
137
|
+
const targeted = components.find(component => component.id === targetComponent && dimensionOf(component));
|
|
138
|
+
if (targeted) return dimensionOf(targeted);
|
|
139
|
+
}
|
|
140
|
+
const active = components.find(component => component.status !== "deferred" && dimensionOf(component));
|
|
141
|
+
if (active) return dimensionOf(active);
|
|
142
|
+
const any = components.find(component => dimensionOf(component));
|
|
143
|
+
return any ? dimensionOf(any) : undefined;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Single source of deep-interview HUD derivation. Reads a complete (normalized)
|
|
148
|
+
* mode-state envelope so recorder, `gjc state write`, reconcile, seed, and handoff
|
|
149
|
+
* all produce identical chips. Topology-aware `target`/`weakest` come from
|
|
150
|
+
* `state.topology`; `legacy_missing` topology omits those chips (no synthetic values).
|
|
151
|
+
*/
|
|
152
|
+
export function deriveDeepInterviewHud(
|
|
153
|
+
payload: Record<string, unknown>,
|
|
154
|
+
options: DeepInterviewHudDeriveOptions = {},
|
|
155
|
+
): WorkflowHudSummary {
|
|
156
|
+
const stateField = diIsPlainObject(payload.state) ? payload.state : {};
|
|
157
|
+
const isNumber = (value: unknown): value is number => typeof value === "number" && Number.isFinite(value);
|
|
158
|
+
const isArray = (value: unknown): value is unknown[] => Array.isArray(value);
|
|
159
|
+
const pick = <T>(key: string, guard: (value: unknown) => value is T): T | undefined => {
|
|
160
|
+
const value = stateField[key] ?? payload[key];
|
|
161
|
+
return guard(value) ? value : undefined;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const phase = options.phase ?? (typeof payload.current_phase === "string" ? payload.current_phase : undefined);
|
|
165
|
+
const rounds = pick("rounds", isArray);
|
|
166
|
+
const ambiguity = pick("current_ambiguity", isNumber) ?? latestScoredAmbiguity(rounds);
|
|
167
|
+
const threshold = pick("threshold", isNumber);
|
|
168
|
+
const rawTopology = diIsPlainObject(stateField.topology)
|
|
169
|
+
? stateField.topology
|
|
170
|
+
: diIsPlainObject(payload.topology)
|
|
171
|
+
? payload.topology
|
|
172
|
+
: undefined;
|
|
173
|
+
// `legacy_missing` topology was never confirmed: omit target/weakest even if stale fields linger.
|
|
174
|
+
const topology = rawTopology && rawTopology.status !== "legacy_missing" ? rawTopology : undefined;
|
|
175
|
+
const targetComponent =
|
|
176
|
+
topology && typeof topology.last_targeted_component_id === "string"
|
|
177
|
+
? topology.last_targeted_component_id
|
|
178
|
+
: undefined;
|
|
179
|
+
const weakestDimension = topology ? weakestDimensionFromTopology(topology, targetComponent) : undefined;
|
|
180
|
+
const specStatus = options.specStatus ?? (typeof payload.spec_status === "string" ? payload.spec_status : undefined);
|
|
181
|
+
|
|
182
|
+
return buildDeepInterviewHudSummary({
|
|
183
|
+
phase,
|
|
184
|
+
ambiguity,
|
|
185
|
+
threshold,
|
|
186
|
+
roundCount: rounds?.length,
|
|
187
|
+
targetComponent,
|
|
188
|
+
weakestDimension,
|
|
189
|
+
specStatus,
|
|
190
|
+
updatedAt: options.updatedAt ?? new Date().toISOString(),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
103
194
|
export function buildRalplanHudSummary(state: RalplanHudState): WorkflowHudSummary {
|
|
104
195
|
const verdict = state.verdict?.toUpperCase();
|
|
105
196
|
const verdictSeverity =
|
|
@@ -118,7 +209,14 @@ export function buildRalplanHudSummary(state: RalplanHudState): WorkflowHudSumma
|
|
|
118
209
|
...gateChips(state, 6),
|
|
119
210
|
chip("stage", state.stage, 10),
|
|
120
211
|
chip("waiting", state.waiting, 20),
|
|
121
|
-
chip(
|
|
212
|
+
chip(
|
|
213
|
+
"iter",
|
|
214
|
+
(state.iterationFromIndex ?? state.iteration) === undefined
|
|
215
|
+
? undefined
|
|
216
|
+
: String(state.iterationFromIndex ?? state.iteration),
|
|
217
|
+
30,
|
|
218
|
+
),
|
|
219
|
+
chip("stages", state.stages, 35),
|
|
122
220
|
chip("verdict", verdict, 40, verdictSeverity),
|
|
123
221
|
]),
|
|
124
222
|
...(state.updatedAt ? { updated_at: state.updatedAt } : {}),
|
|
@@ -136,17 +234,15 @@ export function buildUltragoalHudSummary(state: UltragoalHudState): WorkflowHudS
|
|
|
136
234
|
chip("goals", `${complete}/${total}`, 10),
|
|
137
235
|
chip("current", state.currentGoal ? `${state.currentGoal.id}:${state.currentGoal.title}` : state.status, 20),
|
|
138
236
|
chip("status", state.status, 30, state.status === "complete" ? "success" : undefined),
|
|
237
|
+
chip(
|
|
238
|
+
"ledger",
|
|
239
|
+
state.latestLedgerEvent?.event
|
|
240
|
+
? [state.latestLedgerEvent.event, state.latestLedgerEvent.goalId].filter(Boolean).join(":")
|
|
241
|
+
: undefined,
|
|
242
|
+
35,
|
|
243
|
+
),
|
|
139
244
|
...gateChips(state, 40),
|
|
140
245
|
]),
|
|
141
|
-
details: state.latestLedgerEvent
|
|
142
|
-
? compactChips([
|
|
143
|
-
chip(
|
|
144
|
-
"ledger",
|
|
145
|
-
[state.latestLedgerEvent.event, state.latestLedgerEvent.goalId].filter(Boolean).join(":"),
|
|
146
|
-
100,
|
|
147
|
-
),
|
|
148
|
-
])
|
|
149
|
-
: undefined,
|
|
150
246
|
...(state.updatedAt ? { updated_at: state.updatedAt } : {}),
|
|
151
247
|
};
|
|
152
248
|
}
|
|
@@ -4,6 +4,7 @@ import type { ThinkingLevel } from "@gajae-code/agent-core";
|
|
|
4
4
|
import { type Model, modelsAreEqual } from "@gajae-code/ai";
|
|
5
5
|
import { getOAuthProviders } from "@gajae-code/ai/utils/oauth";
|
|
6
6
|
import { setProjectDir } from "@gajae-code/utils";
|
|
7
|
+
import { jobElapsedMs } from "../async";
|
|
7
8
|
import {
|
|
8
9
|
GJC_MODEL_ASSIGNMENT_TARGET_IDS,
|
|
9
10
|
GJC_MODEL_ASSIGNMENT_TARGETS,
|
|
@@ -506,14 +507,14 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
506
507
|
if (snapshot.running.length > 0) {
|
|
507
508
|
lines.push("", "Running Jobs");
|
|
508
509
|
for (const job of snapshot.running) {
|
|
509
|
-
lines.push(` [${job.id}] ${job.type} (${job.status}) — ${formatDuration(now
|
|
510
|
+
lines.push(` [${job.id}] ${job.type} (${job.status}) — ${formatDuration(jobElapsedMs(job, now))}`);
|
|
510
511
|
lines.push(` ${job.label}`);
|
|
511
512
|
}
|
|
512
513
|
}
|
|
513
514
|
if (snapshot.recent.length > 0) {
|
|
514
515
|
lines.push("", "Recent Jobs");
|
|
515
516
|
for (const job of snapshot.recent) {
|
|
516
|
-
lines.push(` [${job.id}] ${job.type} (${job.status}) — ${formatDuration(now
|
|
517
|
+
lines.push(` [${job.id}] ${job.type} (${job.status}) — ${formatDuration(jobElapsedMs(job, now))}`);
|
|
517
518
|
lines.push(` ${job.label}`);
|
|
518
519
|
}
|
|
519
520
|
}
|
package/src/task/executor.ts
CHANGED
|
@@ -482,11 +482,17 @@ function getUsageTokens(usage: unknown): number {
|
|
|
482
482
|
return firstNumberField(record, ["totalTokens", "total_tokens"]) ?? 0;
|
|
483
483
|
}
|
|
484
484
|
|
|
485
|
-
function createSubagentSettings(baseSettings: Settings): Settings {
|
|
485
|
+
export function createSubagentSettings(baseSettings: Settings): Settings {
|
|
486
486
|
const snapshot: Partial<Record<SettingPath, unknown>> = {};
|
|
487
487
|
for (const key of Object.keys(SETTINGS_SCHEMA) as SettingPath[]) {
|
|
488
488
|
snapshot[key] = baseSettings.get(key);
|
|
489
489
|
}
|
|
490
|
+
// Subagent-scoped service-tier override: "inherit" keeps the snapshotted main
|
|
491
|
+
// session tier; any explicit value applies only to subagent sessions.
|
|
492
|
+
const taskServiceTier = baseSettings.get("task.serviceTier");
|
|
493
|
+
if (taskServiceTier !== "inherit") {
|
|
494
|
+
snapshot.serviceTier = taskServiceTier;
|
|
495
|
+
}
|
|
490
496
|
return Settings.isolated({
|
|
491
497
|
...snapshot,
|
|
492
498
|
"async.enabled": false,
|
|
@@ -1154,6 +1160,15 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
1154
1160
|
resolvedModel: model.id,
|
|
1155
1161
|
});
|
|
1156
1162
|
}
|
|
1163
|
+
// Record which model the subagent actually runs on (and any auth fallback,
|
|
1164
|
+
// see #985) so the subagent panel can surface it to the user.
|
|
1165
|
+
if (model) {
|
|
1166
|
+
AsyncJobManager.instance()?.updateSubagentModel?.(options.subagentId ?? id, {
|
|
1167
|
+
requestedModel: modelSubstitutionWarning?.requested ?? resolvedModelString,
|
|
1168
|
+
effectiveModel: resolvedModelString,
|
|
1169
|
+
modelFellBack: authFallbackUsed === true,
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1157
1172
|
if (model?.contextWindow && model.contextWindow > 0) {
|
|
1158
1173
|
progress.contextWindow = model.contextWindow;
|
|
1159
1174
|
}
|
package/src/task/render.ts
CHANGED
|
@@ -525,6 +525,10 @@ function renderAgentProgress(
|
|
|
525
525
|
expanded: boolean,
|
|
526
526
|
theme: Theme,
|
|
527
527
|
spinnerFrame?: number,
|
|
528
|
+
/** When true, omit wall-clock-derived displays (current-tool elapsed, retry
|
|
529
|
+
* countdown) so the output is a pure function of `progress` — required when the
|
|
530
|
+
* caller caches these lines (the `subagent` await panel). */
|
|
531
|
+
staticTime = false,
|
|
528
532
|
): string[] {
|
|
529
533
|
const lines: string[] = [];
|
|
530
534
|
const prefix = isLast ? theme.fg("dim", theme.tree.last) : theme.fg("dim", theme.tree.branch);
|
|
@@ -587,7 +591,7 @@ function renderAgentProgress(
|
|
|
587
591
|
if (toolDetail) {
|
|
588
592
|
toolLine += `: ${theme.fg("dim", truncateToWidth(replaceTabs(toolDetail), 40))}`;
|
|
589
593
|
}
|
|
590
|
-
if (progress.currentToolStartMs) {
|
|
594
|
+
if (!staticTime && progress.currentToolStartMs) {
|
|
591
595
|
const elapsed = Date.now() - progress.currentToolStartMs;
|
|
592
596
|
if (elapsed > 5000) {
|
|
593
597
|
toolLine += `${theme.sep.dot}${theme.fg("warning", formatDuration(elapsed))}`;
|
|
@@ -610,12 +614,17 @@ function renderAgentProgress(
|
|
|
610
614
|
// long until the next attempt. Without this, the parent UI would just
|
|
611
615
|
// keep spinning while a child sleeps on a 3-hour provider rate-limit.
|
|
612
616
|
if (progress.retryState && progress.status === "running") {
|
|
613
|
-
const remainingMs = Math.max(0, progress.retryState.startedAtMs + progress.retryState.delayMs - Date.now());
|
|
614
|
-
const waitLabel = remainingMs > 0 ? `in ${formatDuration(remainingMs)}` : "now";
|
|
615
617
|
const attemptLabel = progress.retryState.unbounded
|
|
616
618
|
? `attempt ${progress.retryState.attempt}`
|
|
617
619
|
: `${progress.retryState.attempt}/${progress.retryState.maxAttempts}`;
|
|
618
|
-
|
|
620
|
+
// `staticTime` omits the wall-clock countdown so a cached await body stays a
|
|
621
|
+
// pure function of its key (the producer already drops time-only churn).
|
|
622
|
+
let waitLabel = "";
|
|
623
|
+
if (!staticTime) {
|
|
624
|
+
const remainingMs = Math.max(0, progress.retryState.startedAtMs + progress.retryState.delayMs - Date.now());
|
|
625
|
+
waitLabel = remainingMs > 0 ? ` in ${formatDuration(remainingMs)}` : " now";
|
|
626
|
+
}
|
|
627
|
+
const summary = `retrying ${attemptLabel}${waitLabel}: ${truncateToWidth(replaceTabs(progress.retryState.errorMessage), 60)}`;
|
|
619
628
|
lines.push(`${continuePrefix}${theme.tree.hook} ${theme.fg("warning", summary)}`);
|
|
620
629
|
} else if (progress.retryFailure && progress.status !== "running") {
|
|
621
630
|
const summary = `auto-retry gave up after ${progress.retryFailure.attempt} attempt${
|
|
@@ -687,7 +696,7 @@ function renderAgentProgress(
|
|
|
687
696
|
const inflight = progress.inflightTaskDetails;
|
|
688
697
|
if (completedTaskCalls.length > 0 || inflight) {
|
|
689
698
|
const snapshots = inflight ? [...completedTaskCalls, inflight] : completedTaskCalls;
|
|
690
|
-
const nestedLines = renderNestedTaskTree(snapshots, expanded, theme, spinnerFrame);
|
|
699
|
+
const nestedLines = renderNestedTaskTree(snapshots, expanded, theme, spinnerFrame, staticTime);
|
|
691
700
|
for (const line of nestedLines) {
|
|
692
701
|
lines.push(`${continuePrefix}${line}`);
|
|
693
702
|
}
|
|
@@ -712,8 +721,9 @@ export function renderSubagentLiveProgress(
|
|
|
712
721
|
expanded: boolean,
|
|
713
722
|
theme: Theme,
|
|
714
723
|
spinnerFrame?: number,
|
|
724
|
+
staticTime = false,
|
|
715
725
|
): string[] {
|
|
716
|
-
return renderAgentProgress(progress, true, expanded, theme, spinnerFrame);
|
|
726
|
+
return renderAgentProgress(progress, true, expanded, theme, spinnerFrame, staticTime);
|
|
717
727
|
}
|
|
718
728
|
|
|
719
729
|
/**
|
|
@@ -1051,6 +1061,7 @@ function renderNestedTaskTree(
|
|
|
1051
1061
|
expanded: boolean,
|
|
1052
1062
|
theme: Theme,
|
|
1053
1063
|
spinnerFrame?: number,
|
|
1064
|
+
staticTime = false,
|
|
1054
1065
|
): string[] {
|
|
1055
1066
|
const lines: string[] = [];
|
|
1056
1067
|
for (const details of detailsList) {
|
|
@@ -1066,7 +1077,7 @@ function renderNestedTaskTree(
|
|
|
1066
1077
|
if (inflight && inflight.length > 0) {
|
|
1067
1078
|
inflight.forEach((prog, index) => {
|
|
1068
1079
|
const isLast = index === inflight.length - 1;
|
|
1069
|
-
lines.push(...renderAgentProgress(prog, isLast, expanded, theme, spinnerFrame));
|
|
1080
|
+
lines.push(...renderAgentProgress(prog, isLast, expanded, theme, spinnerFrame, staticTime));
|
|
1070
1081
|
});
|
|
1071
1082
|
}
|
|
1072
1083
|
}
|
package/src/tools/ask.ts
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
visibleWidth,
|
|
27
27
|
wrapTextWithAnsi,
|
|
28
28
|
} from "@gajae-code/tui";
|
|
29
|
-
import { prompt, untilAborted } from "@gajae-code/utils";
|
|
29
|
+
import { logger, prompt, untilAborted } from "@gajae-code/utils";
|
|
30
30
|
import * as z from "zod/v4";
|
|
31
31
|
import {
|
|
32
32
|
formatDeepInterviewSelectorPrompt,
|
|
@@ -34,6 +34,8 @@ import {
|
|
|
34
34
|
renderDeepInterviewAskQuestion,
|
|
35
35
|
} from "../deep-interview/render-middleware";
|
|
36
36
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
37
|
+
import { appendOrMergeDeepInterviewRound } from "../gjc-runtime/deep-interview-recorder";
|
|
38
|
+
import { deepInterviewStatePath } from "../gjc-runtime/deep-interview-runtime";
|
|
37
39
|
import { gateAnswerToResult, questionToGate } from "../modes/shared/agent-wire/deep-interview-gate";
|
|
38
40
|
import { getMarkdownTheme, type Theme, theme } from "../modes/theme/theme";
|
|
39
41
|
import askDescription from "../prompts/tools/ask.md" with { type: "text" };
|
|
@@ -41,6 +43,7 @@ import { renderStatusLine } from "../tui";
|
|
|
41
43
|
import type { ToolSession } from ".";
|
|
42
44
|
import { formatErrorMessage, formatMeta, formatTitle } from "./render-utils";
|
|
43
45
|
import { ToolAbortError } from "./tool-errors";
|
|
46
|
+
import { assertUltragoalAskAllowed } from "./ultragoal-ask-guard";
|
|
44
47
|
|
|
45
48
|
// =============================================================================
|
|
46
49
|
// Types
|
|
@@ -50,15 +53,25 @@ const OptionItem = z.object({
|
|
|
50
53
|
label: z.string().describe("display label"),
|
|
51
54
|
});
|
|
52
55
|
|
|
56
|
+
/** Optional structured deep-interview round metadata; when present the round is recorded automatically. */
|
|
57
|
+
const DeepInterviewMeta = z.object({
|
|
58
|
+
round_id: z.string().describe("stable optional round identity").optional(),
|
|
59
|
+
round: z.number().int().nonnegative().describe("round number"),
|
|
60
|
+
component: z.string().min(1).describe("targeted topology component"),
|
|
61
|
+
dimension: z.string().min(1).describe("targeted clarity dimension"),
|
|
62
|
+
ambiguity: z.number().min(0).max(1).describe("ambiguity at ask time (0..1)"),
|
|
63
|
+
});
|
|
64
|
+
|
|
53
65
|
const QuestionItem = z.object({
|
|
54
66
|
id: z.string().describe("question id"),
|
|
55
67
|
question: z.string().describe("question text"),
|
|
56
68
|
options: z.array(OptionItem).describe("available options"),
|
|
57
69
|
multi: z.boolean().describe("allow multiple selections").optional(),
|
|
58
70
|
recommended: z.number().describe("recommended option index").optional(),
|
|
71
|
+
deepInterview: DeepInterviewMeta.describe("optional deep-interview round metadata").optional(),
|
|
59
72
|
});
|
|
60
73
|
|
|
61
|
-
const askSchema = z.object({
|
|
74
|
+
export const askSchema = z.object({
|
|
62
75
|
questions: z.array(QuestionItem).min(1).describe("questions to ask"),
|
|
63
76
|
});
|
|
64
77
|
|
|
@@ -456,6 +469,45 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
456
469
|
TERMINAL.sendNotification("Waiting for input");
|
|
457
470
|
}
|
|
458
471
|
|
|
472
|
+
/**
|
|
473
|
+
* Record a resolved deep-interview round when the question carries structured
|
|
474
|
+
* metadata. The runtime owns durable record/merge semantics; this tool is only the
|
|
475
|
+
* caller. Best-effort: a state-write hiccup must not break the user's answer flow.
|
|
476
|
+
*/
|
|
477
|
+
async #recordDeepInterviewRound(
|
|
478
|
+
q: AskParams["questions"][number],
|
|
479
|
+
selectedOptions: string[],
|
|
480
|
+
customInput: string | undefined,
|
|
481
|
+
): Promise<void> {
|
|
482
|
+
const meta = q.deepInterview;
|
|
483
|
+
if (!meta) return;
|
|
484
|
+
try {
|
|
485
|
+
const cwd = this.session.cwd;
|
|
486
|
+
const sessionId = this.session.getSessionId?.() ?? undefined;
|
|
487
|
+
const statePath = deepInterviewStatePath(cwd, sessionId);
|
|
488
|
+
await appendOrMergeDeepInterviewRound(
|
|
489
|
+
cwd,
|
|
490
|
+
statePath,
|
|
491
|
+
{
|
|
492
|
+
round: meta.round,
|
|
493
|
+
round_id: meta.round_id,
|
|
494
|
+
questionId: q.id,
|
|
495
|
+
questionText: q.question,
|
|
496
|
+
component: meta.component,
|
|
497
|
+
dimension: meta.dimension,
|
|
498
|
+
ambiguity: meta.ambiguity,
|
|
499
|
+
selectedOptions,
|
|
500
|
+
customInput,
|
|
501
|
+
},
|
|
502
|
+
{ sessionId },
|
|
503
|
+
);
|
|
504
|
+
} catch (error) {
|
|
505
|
+
logger.warn(
|
|
506
|
+
`ask: deep-interview round recording failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
459
511
|
async execute(
|
|
460
512
|
_toolCallId: string,
|
|
461
513
|
params: AskParams,
|
|
@@ -463,6 +515,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
463
515
|
_onUpdate?: AgentToolUpdateCallback<AskToolDetails>,
|
|
464
516
|
context?: AgentToolContext,
|
|
465
517
|
): Promise<AgentToolResult<AskToolDetails>> {
|
|
518
|
+
await assertUltragoalAskAllowed(this.session.cwd);
|
|
466
519
|
const gateEmitter = this.session.getWorkflowGateEmitter?.();
|
|
467
520
|
const canUseWorkflowGate = gateEmitter?.isUnattended() === true;
|
|
468
521
|
|
|
@@ -515,6 +568,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
515
568
|
options: q.options,
|
|
516
569
|
multi: q.multi,
|
|
517
570
|
recommended: q.recommended,
|
|
571
|
+
deepInterview: q.deepInterview,
|
|
518
572
|
};
|
|
519
573
|
const answer = await gateEmitter.emitGate(questionToGate(gateQuestion));
|
|
520
574
|
const decoded = gateAnswerToResult(gateQuestion, answer);
|
|
@@ -582,6 +636,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
582
636
|
context?.abort();
|
|
583
637
|
throw new ToolAbortError("Ask tool was cancelled by the user");
|
|
584
638
|
}
|
|
639
|
+
await this.#recordDeepInterviewRound(q, selectedOptions, customInput);
|
|
585
640
|
const details: AskToolDetails = {
|
|
586
641
|
question: q.question,
|
|
587
642
|
options: optionLabels,
|
|
@@ -644,6 +699,8 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
|
644
699
|
customInput,
|
|
645
700
|
};
|
|
646
701
|
|
|
702
|
+
await this.#recordDeepInterviewRound(q, selectedOptions, customInput);
|
|
703
|
+
|
|
647
704
|
if (navAction === "back") {
|
|
648
705
|
questionIndex = Math.max(0, questionIndex - 1);
|
|
649
706
|
continue;
|
package/src/tools/cron.ts
CHANGED
|
@@ -391,7 +391,7 @@ export function calculateCronFireTimeMs(params: {
|
|
|
391
391
|
}
|
|
392
392
|
|
|
393
393
|
function setCronTimeout(callback: () => void, delayMs: number): CronTimerHandle {
|
|
394
|
-
let handle:
|
|
394
|
+
let handle: NodeJS.Timeout | undefined;
|
|
395
395
|
let cleared = false;
|
|
396
396
|
const schedule = (remainingMs: number) => {
|
|
397
397
|
if (cleared) return;
|
package/src/tools/job.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { Component } from "@gajae-code/tui";
|
|
|
3
3
|
import { Text } from "@gajae-code/tui";
|
|
4
4
|
import { prompt } from "@gajae-code/utils";
|
|
5
5
|
import * as z from "zod/v4";
|
|
6
|
-
import { type AsyncJob, AsyncJobManager, isBackgroundJobSupportEnabled } from "../async";
|
|
6
|
+
import { type AsyncJob, AsyncJobManager, isBackgroundJobSupportEnabled, jobElapsedMs } from "../async";
|
|
7
7
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
8
8
|
import type { Theme } from "../modes/theme/theme";
|
|
9
9
|
import jobDescription from "../prompts/tools/job.md" with { type: "text" };
|
|
@@ -257,6 +257,7 @@ export class JobTool implements AgentTool<typeof jobSchema, JobToolDetails> {
|
|
|
257
257
|
status: string;
|
|
258
258
|
label: string;
|
|
259
259
|
startTime: number;
|
|
260
|
+
endTime?: number;
|
|
260
261
|
resultText?: string;
|
|
261
262
|
errorText?: string;
|
|
262
263
|
}[],
|
|
@@ -270,7 +271,7 @@ export class JobTool implements AgentTool<typeof jobSchema, JobToolDetails> {
|
|
|
270
271
|
type: latest.type,
|
|
271
272
|
status: latest.status as JobSnapshot["status"],
|
|
272
273
|
label: latest.label,
|
|
273
|
-
durationMs:
|
|
274
|
+
durationMs: jobElapsedMs(latest, now),
|
|
274
275
|
...(latest.resultText ? { resultText: latest.resultText } : {}),
|
|
275
276
|
...(latest.errorText ? { errorText: latest.errorText } : {}),
|
|
276
277
|
};
|
package/src/tools/monitor.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { logger, prompt } from "@gajae-code/utils";
|
|
|
3
3
|
import * as z from "zod/v4";
|
|
4
4
|
import { AsyncJobManager, isBackgroundJobSupportEnabled } from "../async";
|
|
5
5
|
import monitorDescription from "../prompts/tools/monitor.md" with { type: "text" };
|
|
6
|
+
import { truncateTail } from "../session/streaming-output";
|
|
6
7
|
import { BashTool } from "./bash";
|
|
7
8
|
import type { ToolSession } from "./index";
|
|
8
9
|
import { ToolError } from "./tool-errors";
|
|
@@ -48,6 +49,8 @@ export interface MonitorToolDetails {
|
|
|
48
49
|
|
|
49
50
|
const MONITOR_LABEL_MAX = 120;
|
|
50
51
|
const MAX_PENDING_MONITOR_NOTIFICATIONS = 3;
|
|
52
|
+
const MONITOR_NOTIFICATION_LINE_MAX_BYTES = 16 * 1024;
|
|
53
|
+
const MONITOR_NOTIFICATION_LINE_MAX_LINES = 20;
|
|
51
54
|
|
|
52
55
|
function buildMonitorLabel(params: MonitorParams): string {
|
|
53
56
|
const base = `[monitor:${params.kind}] ${params.description}`;
|
|
@@ -55,6 +58,34 @@ function buildMonitorLabel(params: MonitorParams): string {
|
|
|
55
58
|
return `${base.slice(0, MONITOR_LABEL_MAX - 3)}...`;
|
|
56
59
|
}
|
|
57
60
|
|
|
61
|
+
function formatMonitorNotificationLine(line: string): {
|
|
62
|
+
content: string;
|
|
63
|
+
truncated: boolean;
|
|
64
|
+
totalBytes: number;
|
|
65
|
+
outputBytes: number;
|
|
66
|
+
} {
|
|
67
|
+
const truncation = truncateTail(line, {
|
|
68
|
+
maxBytes: MONITOR_NOTIFICATION_LINE_MAX_BYTES,
|
|
69
|
+
maxLines: MONITOR_NOTIFICATION_LINE_MAX_LINES,
|
|
70
|
+
});
|
|
71
|
+
const outputBytes = truncation.outputBytes ?? truncation.totalBytes;
|
|
72
|
+
if (!truncation.truncated) {
|
|
73
|
+
return {
|
|
74
|
+
content: truncation.content,
|
|
75
|
+
truncated: false,
|
|
76
|
+
totalBytes: truncation.totalBytes,
|
|
77
|
+
outputBytes,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const notice = `[Monitor output truncated: showing last ${outputBytes} of ${truncation.totalBytes} bytes]`;
|
|
81
|
+
return {
|
|
82
|
+
content: `${truncation.content}\n${notice}`,
|
|
83
|
+
truncated: true,
|
|
84
|
+
totalBytes: truncation.totalBytes,
|
|
85
|
+
outputBytes,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
58
89
|
export class MonitorTool implements AgentTool<typeof monitorSchema, MonitorToolDetails> {
|
|
59
90
|
readonly name = "monitor";
|
|
60
91
|
readonly label = "Monitor";
|
|
@@ -130,7 +161,8 @@ export class MonitorTool implements AgentTool<typeof monitorSchema, MonitorToolD
|
|
|
130
161
|
if (controller.closed) return;
|
|
131
162
|
const notificationId = `${jobId}:${sequence}`;
|
|
132
163
|
const suffix = count > 0 ? `\n(+${count} earlier lines)` : "";
|
|
133
|
-
const
|
|
164
|
+
const notificationLine = formatMonitorNotificationLine(line);
|
|
165
|
+
const content = `<task-notification>\nMonitor task ${jobId} (${params.kind}: ${params.description}) emitted latest state:\n${notificationLine.content}${suffix}\n</task-notification>`;
|
|
134
166
|
const details = {
|
|
135
167
|
taskId: jobId,
|
|
136
168
|
kind: params.kind,
|
|
@@ -139,6 +171,9 @@ export class MonitorTool implements AgentTool<typeof monitorSchema, MonitorToolD
|
|
|
139
171
|
notificationId,
|
|
140
172
|
sequence,
|
|
141
173
|
coalescedCount: count,
|
|
174
|
+
outputTruncated: notificationLine.truncated,
|
|
175
|
+
outputTotalBytes: notificationLine.totalBytes,
|
|
176
|
+
outputBytes: notificationLine.outputBytes,
|
|
142
177
|
};
|
|
143
178
|
pendingNotifications += 1;
|
|
144
179
|
if (pendingNotifications > MAX_PENDING_MONITOR_NOTIFICATIONS) {
|