@gajae-code/coding-agent 0.12.1 → 0.12.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 +30 -0
- package/dist/types/config/settings-schema.d.ts +2 -2
- package/dist/types/gjc-runtime/ralplan-review-conflicts.d.ts +114 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +5 -3
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +6 -0
- package/dist/types/modes/components/irc-sidebar.d.ts +16 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/event-controller.d.ts +1 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +9 -0
- package/dist/types/modes/theme/theme.d.ts +3 -1
- package/dist/types/modes/types.d.ts +32 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -2
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +22 -3
- package/dist/types/task/index.d.ts +4 -0
- package/dist/types/task/output-manager.d.ts +1 -0
- package/dist/types/task/receipt.d.ts +2 -0
- package/dist/types/tools/index.d.ts +2 -0
- package/package.json +8 -8
- package/scripts/dogfood-ralplan-review-conflicts.ts +272 -0
- package/scripts/generate-sdk-operation-inventory.ts +2 -0
- package/scripts/run-acp-conformance.ts +5 -0
- package/src/async/job-manager.ts +22 -20
- package/src/config/settings-schema.ts +2 -2
- package/src/debug/index.ts +42 -17
- package/src/defaults/gjc/skills/ralplan/SKILL.md +9 -7
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -2
- package/src/gjc-runtime/launch-worktree.ts +103 -1
- package/src/gjc-runtime/ledger-event-renderer.ts +1 -0
- package/src/gjc-runtime/ralplan-review-conflicts.ts +472 -0
- package/src/gjc-runtime/ralplan-runtime.ts +80 -10
- package/src/gjc-runtime/ultragoal-evidence.ts +6 -74
- package/src/gjc-runtime/ultragoal-runtime.ts +236 -3
- package/src/gjc-runtime/workflow-manifest.generated.json +29 -0
- package/src/gjc-runtime/workflow-manifest.ts +17 -2
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/modes/DESIGN.md +53 -0
- package/src/modes/components/irc-sidebar.ts +30 -1
- package/src/modes/components/tool-status-header.ts +0 -1
- package/src/modes/controllers/command-controller.ts +38 -34
- package/src/modes/controllers/event-controller.ts +27 -16
- package/src/modes/controllers/extension-ui-controller.ts +186 -34
- package/src/modes/controllers/selector-controller.ts +115 -22
- package/src/modes/interactive-mode.ts +189 -62
- package/src/modes/jobs-observer.ts +1 -1
- package/src/modes/theme/theme.ts +11 -9
- package/src/modes/types.ts +88 -0
- package/src/sdk/broker/lifecycle.ts +18 -13
- package/src/sdk/bus/chat-daemon-control.ts +4 -3
- package/src/sdk/bus/telegram-daemon-contract.ts +3 -2
- package/src/sdk/protocol/operation-inventory.generated.json +11 -0
- package/src/sdk/session.ts +1 -0
- package/src/session/agent-session.ts +22 -1
- package/src/session/internal/managed-session-scope.ts +158 -107
- package/src/session/internal/managed-session-storage.ts +445 -57
- package/src/session/session-storage.ts +29 -44
- package/src/task/index.ts +257 -31
- package/src/task/output-manager.ts +24 -14
- package/src/task/receipt.ts +11 -1
- package/src/tools/index.ts +2 -0
|
@@ -43,7 +43,13 @@ import {
|
|
|
43
43
|
setTheme,
|
|
44
44
|
theme,
|
|
45
45
|
} from "../../modes/theme/theme";
|
|
46
|
-
import
|
|
46
|
+
import {
|
|
47
|
+
clearInteractiveActivityLoaders,
|
|
48
|
+
type InteractiveModeContext,
|
|
49
|
+
type OAuthSelectorOptions,
|
|
50
|
+
stopInteractiveActivityIndicator,
|
|
51
|
+
suspendInteractiveActivityIndicator,
|
|
52
|
+
} from "../../modes/types";
|
|
47
53
|
import { getNotificationConfig, isTelegramConfigured, maskToken } from "../../sdk/bus/config";
|
|
48
54
|
import {
|
|
49
55
|
clearTelegramActivationMarker,
|
|
@@ -167,7 +173,7 @@ import type { JobsObserver } from "../jobs-observer";
|
|
|
167
173
|
import type { SessionObserverRegistry } from "../session-observer-registry";
|
|
168
174
|
import type { TasksAggregator } from "../tasks-aggregator";
|
|
169
175
|
import type { TranscriptItemRegistry } from "../transcript-item-registry";
|
|
170
|
-
import { acquireResumeProgressLease } from "../utils/ui-helpers";
|
|
176
|
+
import { acquireResumeProgressLease, type ResumeProgressLease } from "../utils/ui-helpers";
|
|
171
177
|
|
|
172
178
|
const CALLBACK_SERVER_PROVIDERS = new Set<string>([
|
|
173
179
|
"anthropic",
|
|
@@ -197,7 +203,6 @@ export function buildStatusLineSettings(settingsInstance: Settings): StatusLineS
|
|
|
197
203
|
rightSegments: settingsInstance.get("statusLine.rightSegments"),
|
|
198
204
|
separator: settingsInstance.get("statusLine.separator"),
|
|
199
205
|
showHookStatus: settingsInstance.get("statusLine.showHookStatus"),
|
|
200
|
-
showActionHints: settingsInstance.get("statusLine.showActionHints"),
|
|
201
206
|
sessionAccent: settingsInstance.get("statusLine.sessionAccent"),
|
|
202
207
|
maxRows: settingsInstance.get("statusLine.maxRows"),
|
|
203
208
|
segmentOptions: settingsInstance.get("statusLine.segmentOptions"),
|
|
@@ -821,7 +826,9 @@ export class SelectorController {
|
|
|
821
826
|
* @param create Factory that receives a `done` callback and returns the component and focus target
|
|
822
827
|
*/
|
|
823
828
|
showSelector(create: (done: () => void) => { component: Component; focus: Component }): void {
|
|
829
|
+
if (this.ctx.isStopped?.()) return;
|
|
824
830
|
const done = () => {
|
|
831
|
+
if (this.ctx.isStopped?.()) return;
|
|
825
832
|
// Prefer the pet-aware composer restore (InteractiveMode.restoreComposer); fall back
|
|
826
833
|
// to a plain editor swap for contexts that predate it (e.g. lightweight test doubles).
|
|
827
834
|
if (typeof this.ctx.restoreComposer === "function") {
|
|
@@ -1566,7 +1573,8 @@ export class SelectorController {
|
|
|
1566
1573
|
this.ctx.rebuildChatFromMessages("reconcile-same-transcript");
|
|
1567
1574
|
break;
|
|
1568
1575
|
case "theme": {
|
|
1569
|
-
setTheme(value as string, true).then(result => {
|
|
1576
|
+
setTheme(value as string, true, { shouldApply: () => !this.ctx.isStopped?.() }).then(result => {
|
|
1577
|
+
if (this.ctx.isStopped?.()) return;
|
|
1570
1578
|
this.ctx.statusLine.invalidate();
|
|
1571
1579
|
this.ctx.updateEditorTopBorder();
|
|
1572
1580
|
this.ctx.ui.invalidate();
|
|
@@ -1626,13 +1634,16 @@ export class SelectorController {
|
|
|
1626
1634
|
this.ctx.session.agent.repetitionPenalty = repetitionPenalty >= 0 ? repetitionPenalty : undefined;
|
|
1627
1635
|
break;
|
|
1628
1636
|
}
|
|
1637
|
+
case "statusLine.showActionHints": {
|
|
1638
|
+
this.ctx.updateEditorChrome();
|
|
1639
|
+
break;
|
|
1640
|
+
}
|
|
1629
1641
|
case "statusLinePreset":
|
|
1630
1642
|
case "statusLine.preset":
|
|
1631
1643
|
case "statusLineSeparator":
|
|
1632
1644
|
case "statusLine.separator":
|
|
1633
1645
|
case "statusLineShowHooks":
|
|
1634
1646
|
case "statusLine.showHookStatus":
|
|
1635
|
-
case "statusLine.showActionHints":
|
|
1636
1647
|
case "statusLine.sessionAccent":
|
|
1637
1648
|
case "statusLine.maxRows":
|
|
1638
1649
|
case "statusLine.leftSegments":
|
|
@@ -2138,12 +2149,14 @@ export class SelectorController {
|
|
|
2138
2149
|
|
|
2139
2150
|
// Set up escape handler and loader if summarizing
|
|
2140
2151
|
let summaryLoader: Loader | undefined;
|
|
2152
|
+
let releaseActivityIndicator: (() => void) | undefined;
|
|
2141
2153
|
const originalOnEscape = this.ctx.editor.onEscape;
|
|
2142
2154
|
|
|
2143
2155
|
if (wantsSummary) {
|
|
2144
2156
|
this.ctx.editor.onEscape = () => {
|
|
2145
2157
|
this.ctx.session.abortBranchSummary();
|
|
2146
2158
|
};
|
|
2159
|
+
releaseActivityIndicator = suspendInteractiveActivityIndicator(this.ctx);
|
|
2147
2160
|
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
2148
2161
|
summaryLoader = new Loader(
|
|
2149
2162
|
this.ctx.ui,
|
|
@@ -2161,6 +2174,7 @@ export class SelectorController {
|
|
|
2161
2174
|
summarize: wantsSummary,
|
|
2162
2175
|
customInstructions,
|
|
2163
2176
|
});
|
|
2177
|
+
if (this.ctx.isStopped?.()) return;
|
|
2164
2178
|
|
|
2165
2179
|
if (result.aborted) {
|
|
2166
2180
|
// Summarization aborted - re-show tree selector
|
|
@@ -2176,18 +2190,21 @@ export class SelectorController {
|
|
|
2176
2190
|
// Update UI — pass the context built by navigateTree to skip a second O(N) walk.
|
|
2177
2191
|
this.ctx.rebuildInitialMessages("reconcile-same-transcript", result.sessionContext);
|
|
2178
2192
|
await this.ctx.reloadTodos();
|
|
2193
|
+
if (this.ctx.isStopped?.()) return;
|
|
2179
2194
|
if (result.editorText && !this.ctx.editor.getText().trim()) {
|
|
2180
2195
|
this.ctx.editor.setText(result.editorText);
|
|
2181
2196
|
}
|
|
2182
2197
|
this.ctx.showStatus("Navigated to selected point");
|
|
2183
2198
|
} catch (error) {
|
|
2199
|
+
if (this.ctx.isStopped?.()) return;
|
|
2184
2200
|
this.ctx.showError(error instanceof Error ? error.message : String(error));
|
|
2185
2201
|
} finally {
|
|
2186
2202
|
if (summaryLoader) {
|
|
2187
2203
|
summaryLoader.stop();
|
|
2188
|
-
this.ctx.statusContainer.clear();
|
|
2204
|
+
if (!this.ctx.isStopped?.()) this.ctx.statusContainer.clear();
|
|
2189
2205
|
}
|
|
2190
|
-
this.ctx.editor.onEscape = originalOnEscape;
|
|
2206
|
+
if (!this.ctx.isStopped?.()) this.ctx.editor.onEscape = originalOnEscape;
|
|
2207
|
+
releaseActivityIndicator?.();
|
|
2191
2208
|
}
|
|
2192
2209
|
},
|
|
2193
2210
|
() => {
|
|
@@ -2206,6 +2223,7 @@ export class SelectorController {
|
|
|
2206
2223
|
|
|
2207
2224
|
async showSessionSelector(): Promise<void> {
|
|
2208
2225
|
const sessions = await this.ctx.sessionManager.listForResumePickerReadOnly();
|
|
2226
|
+
if (this.ctx.isStopped?.()) return;
|
|
2209
2227
|
this.showSelector(done => {
|
|
2210
2228
|
const selector = new SessionSelectorComponent(
|
|
2211
2229
|
sessions,
|
|
@@ -2240,12 +2258,9 @@ export class SelectorController {
|
|
|
2240
2258
|
});
|
|
2241
2259
|
}
|
|
2242
2260
|
|
|
2243
|
-
#clearTransientSessionUi(): void {
|
|
2244
|
-
if (this.ctx
|
|
2245
|
-
|
|
2246
|
-
this.ctx.loadingAnimation = undefined;
|
|
2247
|
-
}
|
|
2248
|
-
this.ctx.statusContainer.clear();
|
|
2261
|
+
#clearTransientSessionUi(options?: { restoreBackground?: boolean; clearSpecializedLoaders?: boolean }): void {
|
|
2262
|
+
if (options?.clearSpecializedLoaders) clearInteractiveActivityLoaders(this.ctx);
|
|
2263
|
+
stopInteractiveActivityIndicator(this.ctx, { restoreBackground: options?.restoreBackground });
|
|
2249
2264
|
this.ctx.pendingMessagesContainer.clear();
|
|
2250
2265
|
this.ctx.compactionQueuedMessages = [];
|
|
2251
2266
|
this.ctx.streamingComponent = undefined;
|
|
@@ -2278,6 +2293,7 @@ export class SelectorController {
|
|
|
2278
2293
|
}
|
|
2279
2294
|
|
|
2280
2295
|
const detached = await this.ctx.session.newSession();
|
|
2296
|
+
if (this.ctx.isStopped?.()) return false;
|
|
2281
2297
|
if (!detached) {
|
|
2282
2298
|
return false;
|
|
2283
2299
|
}
|
|
@@ -2285,28 +2301,50 @@ export class SelectorController {
|
|
|
2285
2301
|
|
|
2286
2302
|
this.#refreshSessionTerminalTitle();
|
|
2287
2303
|
|
|
2288
|
-
this.#clearTransientSessionUi();
|
|
2304
|
+
this.#clearTransientSessionUi({ clearSpecializedLoaders: true });
|
|
2289
2305
|
this.ctx.statusLine.invalidate();
|
|
2290
2306
|
this.ctx.statusLine.setSessionStartTime(Date.now());
|
|
2291
2307
|
this.ctx.updateEditorTopBorder();
|
|
2292
2308
|
this.ctx.updateEditorBorderColor();
|
|
2293
2309
|
this.ctx.rebuildInitialMessages("replace-identity");
|
|
2294
2310
|
await this.ctx.reloadTodos();
|
|
2311
|
+
if (this.ctx.isStopped?.()) return false;
|
|
2295
2312
|
this.ctx.ui.requestRender();
|
|
2296
2313
|
return true;
|
|
2297
2314
|
}
|
|
2298
2315
|
|
|
2316
|
+
/**
|
|
2317
|
+
* Guards `handleResumeSession` against overlapping resumes.
|
|
2318
|
+
*
|
|
2319
|
+
* `acquireResumeProgressLease` only owns the spinner, so it cannot serialize
|
|
2320
|
+
* two resumes, and the session picker dispatches resume through a
|
|
2321
|
+
* void-returning `onSelect` callback — a second selection would otherwise
|
|
2322
|
+
* start a concurrent transition whose rejection nobody awaits.
|
|
2323
|
+
*/
|
|
2324
|
+
#resumeInFlight = false;
|
|
2325
|
+
|
|
2299
2326
|
async handleResumeSession(sessionPath: string): Promise<void> {
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2327
|
+
if (this.#resumeInFlight) {
|
|
2328
|
+
this.ctx.showStatus("Resume already in progress");
|
|
2329
|
+
return;
|
|
2330
|
+
}
|
|
2331
|
+
this.#resumeInFlight = true;
|
|
2332
|
+
const releaseActivityIndicator = suspendInteractiveActivityIndicator(this.ctx);
|
|
2333
|
+
// Everything below runs inside the try so a synchronous failure (a throwing
|
|
2334
|
+
// session-manager accessor, an unavailable status rail) can never strand the
|
|
2335
|
+
// guard and silently disable every later resume.
|
|
2336
|
+
let progressLease: ResumeProgressLease | undefined;
|
|
2303
2337
|
try {
|
|
2338
|
+
const previousSessionId = this.ctx.sessionManager.getSessionId();
|
|
2339
|
+
progressLease = acquireResumeProgressLease(this.ctx);
|
|
2304
2340
|
await progressLease.committed;
|
|
2341
|
+
if (this.ctx.isStopped?.()) return;
|
|
2305
2342
|
const migrationPolicy =
|
|
2306
2343
|
this.ctx.settings?.get("session.directoryMigration") === "disabled" ? "disabled" : "copy-retain";
|
|
2307
2344
|
let writableSessionPath = sessionPath;
|
|
2308
2345
|
if (this.ctx.sessionManager.isManagedDestination()) {
|
|
2309
2346
|
const inspection = await SessionManager.inspectSessionTailReadOnly(sessionPath);
|
|
2347
|
+
if (this.ctx.isStopped?.()) return;
|
|
2310
2348
|
if (inspection.kind === "error")
|
|
2311
2349
|
throw new Error(`Could not inspect selected session: ${inspection.reason}`);
|
|
2312
2350
|
writableSessionPath = await this.ctx.sessionManager.prepareManagedCandidateForStrictAdoption(
|
|
@@ -2314,14 +2352,66 @@ export class SelectorController {
|
|
|
2314
2352
|
migrationPolicy,
|
|
2315
2353
|
inspection.identity,
|
|
2316
2354
|
);
|
|
2355
|
+
if (this.ctx.isStopped?.()) return;
|
|
2317
2356
|
}
|
|
2318
2357
|
// Switch session via AgentSession (emits hook and tool session events)
|
|
2319
|
-
|
|
2320
|
-
|
|
2358
|
+
let switched: boolean;
|
|
2359
|
+
let transitionMutationStarted = false;
|
|
2360
|
+
try {
|
|
2361
|
+
switched = await this.ctx.session.switchSession(writableSessionPath, {
|
|
2321
2362
|
transition: { origin: INTERACTIVE_SELECTOR_RESUME_ORIGIN },
|
|
2322
|
-
|
|
2323
|
-
|
|
2363
|
+
onTransitionMutationStarted: () => {
|
|
2364
|
+
transitionMutationStarted = true;
|
|
2365
|
+
},
|
|
2366
|
+
});
|
|
2367
|
+
if (this.ctx.isStopped?.()) return;
|
|
2368
|
+
} catch (error) {
|
|
2369
|
+
if (this.ctx.isStopped?.()) return;
|
|
2370
|
+
// `switchSession` opens with `#beginSessionTransition`, which throws
|
|
2371
|
+
// `{ code: "busy" }` while another transition (compaction, handoff, fork,
|
|
2372
|
+
// another switch, …) owns the session. Resume is a UI action dispatched
|
|
2373
|
+
// through a void callback, so report that as status instead of rejecting a
|
|
2374
|
+
// promise nobody awaits. Admission-busy never acquired the lease, so the
|
|
2375
|
+
// owner's transient state — including the `compactionQueuedMessages` that
|
|
2376
|
+
// hold input typed during compaction — must survive untouched.
|
|
2377
|
+
const typed = error as { code?: unknown } | undefined;
|
|
2378
|
+
if (typed?.code === "busy") {
|
|
2379
|
+
this.ctx.showStatus("Another session operation is already in progress");
|
|
2380
|
+
return;
|
|
2381
|
+
}
|
|
2382
|
+
// A rejection can occur either before the transition mutates the live session
|
|
2383
|
+
// (for example, a rejecting `session_before_switch` hook) or after disconnect/
|
|
2384
|
+
// abort has begun. Preserve the current session's transient UI in the former
|
|
2385
|
+
// case; reconcile only state invalidated by a transition that actually mutated.
|
|
2386
|
+
// Reconciliation stays best-effort so cleanup failure never replaces the
|
|
2387
|
+
// original switch failure.
|
|
2388
|
+
if (transitionMutationStarted) {
|
|
2389
|
+
try {
|
|
2390
|
+
progressLease.clear();
|
|
2391
|
+
this.#clearTransientSessionUi({ restoreBackground: false, clearSpecializedLoaders: true });
|
|
2392
|
+
} catch {
|
|
2393
|
+
logger.warn("Resume transient-UI reconciliation failed after a session switch error", {
|
|
2394
|
+
classification: "resume-cleanup-failed",
|
|
2395
|
+
});
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
throw error;
|
|
2399
|
+
}
|
|
2400
|
+
// `#clearTransientSessionUi` drops `compactionQueuedMessages`, so it must stay
|
|
2401
|
+
// untouched on the paths that never acquired the session-transition lease:
|
|
2402
|
+
// admission-busy above and managed-candidate preparation before it.
|
|
2403
|
+
//
|
|
2404
|
+
// A pre-mutation hook cancellation preserves the current session and all of
|
|
2405
|
+
// its foreground/retry UI. A rollback after disconnect/abort must clear it.
|
|
2406
|
+
if (!switched) {
|
|
2407
|
+
if (transitionMutationStarted) {
|
|
2408
|
+
progressLease.clear();
|
|
2409
|
+
this.#clearTransientSessionUi({ restoreBackground: false, clearSpecializedLoaders: true });
|
|
2410
|
+
}
|
|
2324
2411
|
return;
|
|
2412
|
+
}
|
|
2413
|
+
progressLease.clear();
|
|
2414
|
+
this.#clearTransientSessionUi({ restoreBackground: false, clearSpecializedLoaders: true });
|
|
2325
2415
|
const switchingToDifferentSession = previousSessionId !== this.ctx.sessionManager.getSessionId();
|
|
2326
2416
|
if (switchingToDifferentSession) this.ctx.resetIrcSidebarSession();
|
|
2327
2417
|
this.#refreshSessionTerminalTitle();
|
|
@@ -2331,10 +2421,13 @@ export class SelectorController {
|
|
|
2331
2421
|
switchingToDifferentSession ? "replace-identity" : "reconcile-same-transcript",
|
|
2332
2422
|
);
|
|
2333
2423
|
await this.ctx.reloadTodos();
|
|
2424
|
+
if (this.ctx.isStopped?.()) return;
|
|
2334
2425
|
this.ctx.showStatus("Resumed session");
|
|
2335
2426
|
this.#maybePromptResumeModelChoice();
|
|
2336
2427
|
} finally {
|
|
2337
|
-
progressLease
|
|
2428
|
+
progressLease?.clear();
|
|
2429
|
+
this.#resumeInFlight = false;
|
|
2430
|
+
releaseActivityIndicator();
|
|
2338
2431
|
}
|
|
2339
2432
|
}
|
|
2340
2433
|
|