@gajae-code/coding-agent 0.10.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/types/cli.d.ts +1 -0
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/policy.d.ts +3 -0
- package/dist/types/coordinator-mcp/server.d.ts +17 -5
- package/dist/types/coordinator-mcp/session-reaper.d.ts +53 -0
- package/dist/types/extensibility/extensions/types.d.ts +3 -1
- package/dist/types/modes/components/status-line/types.d.ts +1 -1
- package/dist/types/modes/components/status-line.d.ts +0 -10
- package/dist/types/modes/print-mode.d.ts +3 -4
- package/dist/types/modes/rpc/rpc-client.d.ts +7 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +2 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +17 -1
- package/dist/types/modes/utils/context-usage.d.ts +4 -19
- package/dist/types/session/agent-session.d.ts +14 -1
- package/dist/types/session/context-estimation.d.ts +31 -0
- package/package.json +7 -7
- package/src/cli.ts +1 -0
- package/src/config/settings.ts +96 -45
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/policy.ts +30 -0
- package/src/coordinator-mcp/server.ts +294 -66
- package/src/coordinator-mcp/session-reaper.ts +129 -0
- package/src/extensibility/extensions/runner.ts +1 -1
- package/src/extensibility/extensions/types.ts +3 -1
- package/src/internal-urls/docs-index.generated.ts +4 -4
- package/src/main.ts +3 -5
- package/src/modes/acp/acp-agent.ts +2 -2
- package/src/modes/components/footer.ts +3 -3
- package/src/modes/components/status-line/segments.ts +13 -6
- package/src/modes/components/status-line/types.ts +1 -1
- package/src/modes/components/status-line.ts +4 -171
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +16 -0
- package/src/modes/print-mode.ts +228 -92
- package/src/modes/rpc/rpc-client.ts +58 -1
- package/src/modes/rpc/rpc-mode.ts +286 -85
- package/src/modes/rpc/rpc-types.ts +15 -1
- package/src/modes/shared/agent-wire/command-dispatch.ts +33 -1
- package/src/modes/shared/agent-wire/command-validation.ts +10 -0
- package/src/modes/shared/agent-wire/scopes.ts +1 -0
- package/src/modes/utils/context-usage.ts +56 -92
- package/src/session/agent-session.ts +287 -85
- package/src/session/context-estimation.ts +91 -0
- package/src/slash-commands/builtin-registry.ts +12 -2
- package/src/slash-commands/helpers/context-report.ts +26 -4
package/src/config/settings.ts
CHANGED
|
@@ -55,6 +55,12 @@ export interface RawSettings {
|
|
|
55
55
|
[key: string]: unknown;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
type SettingsPatch = {
|
|
59
|
+
readonly path: string;
|
|
60
|
+
readonly value: unknown;
|
|
61
|
+
readonly generation: number;
|
|
62
|
+
};
|
|
63
|
+
|
|
58
64
|
export interface SettingsOptions {
|
|
59
65
|
/** Current working directory for project settings discovery */
|
|
60
66
|
cwd?: string;
|
|
@@ -207,12 +213,14 @@ export class Settings {
|
|
|
207
213
|
/** Merged view (global + project + overrides) */
|
|
208
214
|
#merged: RawSettings = {};
|
|
209
215
|
|
|
210
|
-
/**
|
|
211
|
-
#modified = new
|
|
216
|
+
/** Latest dirty patch for each path, owned by its generation. */
|
|
217
|
+
#modified = new Map<string, SettingsPatch>();
|
|
218
|
+
#nextGeneration = 0;
|
|
212
219
|
|
|
213
220
|
/** Pending save (debounced) */
|
|
214
221
|
#saveTimer?: NodeJS.Timeout;
|
|
215
|
-
#
|
|
222
|
+
#saveTail: Promise<void> = Promise.resolve();
|
|
223
|
+
#globalModelRoleTail: Promise<void> = Promise.resolve();
|
|
216
224
|
|
|
217
225
|
/** Whether to persist changes */
|
|
218
226
|
#persist: boolean;
|
|
@@ -320,9 +328,13 @@ export class Settings {
|
|
|
320
328
|
*/
|
|
321
329
|
set<P extends SettingPath>(path: P, value: SettingValue<P>): void {
|
|
322
330
|
const prev = this.get(path);
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
331
|
+
const patch: SettingsPatch = {
|
|
332
|
+
path,
|
|
333
|
+
value: structuredClone(value),
|
|
334
|
+
generation: ++this.#nextGeneration,
|
|
335
|
+
};
|
|
336
|
+
setByPath(this.#global, path.split("."), structuredClone(patch.value));
|
|
337
|
+
this.#modified.set(path, patch);
|
|
326
338
|
this.#rebuildMerged();
|
|
327
339
|
this.#queueSave();
|
|
328
340
|
|
|
@@ -366,12 +378,8 @@ export class Settings {
|
|
|
366
378
|
clearTimeout(this.#saveTimer);
|
|
367
379
|
this.#saveTimer = undefined;
|
|
368
380
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
372
|
-
if (this.#modified.size > 0) {
|
|
373
|
-
await this.#saveNow();
|
|
374
|
-
}
|
|
381
|
+
const save = this.#modified.size > 0 ? this.#saveNow() : this.#saveTail;
|
|
382
|
+
await save;
|
|
375
383
|
}
|
|
376
384
|
|
|
377
385
|
/**
|
|
@@ -385,12 +393,8 @@ export class Settings {
|
|
|
385
393
|
clearTimeout(this.#saveTimer);
|
|
386
394
|
this.#saveTimer = undefined;
|
|
387
395
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
}
|
|
391
|
-
if (this.#modified.size > 0) {
|
|
392
|
-
await this.#saveNow({ throwOnError: true });
|
|
393
|
-
}
|
|
396
|
+
const save = this.#modified.size > 0 ? this.#saveNow({ throwOnError: true }) : this.#saveTail;
|
|
397
|
+
await save;
|
|
394
398
|
}
|
|
395
399
|
|
|
396
400
|
async cloneForCwd(cwd: string): Promise<Settings> {
|
|
@@ -480,7 +484,6 @@ export class Settings {
|
|
|
480
484
|
* Set a model role (helper for modelRoles record).
|
|
481
485
|
*/
|
|
482
486
|
setModelRole(role: ModelRole | string, modelId: string): void {
|
|
483
|
-
const current = shallowStringRecord(getByPath(this.#global, ["modelRoles"]));
|
|
484
487
|
const runtimeOverrides = getByPath(this.#overrides, ["modelRoles"]);
|
|
485
488
|
const updateRuntimeOverride =
|
|
486
489
|
!!runtimeOverrides &&
|
|
@@ -488,12 +491,55 @@ export class Settings {
|
|
|
488
491
|
!Array.isArray(runtimeOverrides) &&
|
|
489
492
|
Object.hasOwn(runtimeOverrides, role);
|
|
490
493
|
|
|
491
|
-
this.
|
|
494
|
+
this.setGlobalModelRole(role, modelId);
|
|
492
495
|
|
|
493
496
|
if (updateRuntimeOverride) {
|
|
494
497
|
this.override("modelRoles", { ...shallowStringRecord(runtimeOverrides), [role]: modelId });
|
|
495
498
|
}
|
|
496
499
|
}
|
|
500
|
+
|
|
501
|
+
setGlobalModelRole(role: ModelRole | string, modelId: string | undefined): void {
|
|
502
|
+
const current = shallowStringRecord(getByPath(this.#global, ["modelRoles"]));
|
|
503
|
+
if (modelId === undefined) {
|
|
504
|
+
const { [role]: _removed, ...remaining } = current;
|
|
505
|
+
this.set("modelRoles", remaining);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
this.set("modelRoles", { ...current, [role]: modelId });
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
setGlobalModelRoleAndFlush(role: ModelRole | string, modelId: string | undefined): Promise<void> {
|
|
512
|
+
const transaction = this.#globalModelRoleTail.then(async () => {
|
|
513
|
+
const hadModelRoles = Object.hasOwn(this.#global, "modelRoles");
|
|
514
|
+
const previousModelRoles = structuredClone(this.#global.modelRoles);
|
|
515
|
+
const previousPatch = this.#modified.get("modelRoles");
|
|
516
|
+
this.setGlobalModelRole(role, modelId);
|
|
517
|
+
const generation = this.#modified.get("modelRoles")?.generation;
|
|
518
|
+
if (this.#saveTimer) {
|
|
519
|
+
clearTimeout(this.#saveTimer);
|
|
520
|
+
this.#saveTimer = undefined;
|
|
521
|
+
}
|
|
522
|
+
const save = this.#saveNow({ throwOnError: true });
|
|
523
|
+
try {
|
|
524
|
+
await save;
|
|
525
|
+
} catch (error) {
|
|
526
|
+
const currentPatch = this.#modified.get("modelRoles");
|
|
527
|
+
if (currentPatch?.generation === generation) {
|
|
528
|
+
if (hadModelRoles) this.#global.modelRoles = previousModelRoles;
|
|
529
|
+
else delete this.#global.modelRoles;
|
|
530
|
+
if (previousPatch) this.#modified.set("modelRoles", previousPatch);
|
|
531
|
+
else this.#modified.delete("modelRoles");
|
|
532
|
+
this.#rebuildMerged();
|
|
533
|
+
}
|
|
534
|
+
throw error;
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
this.#globalModelRoleTail = transaction.then(
|
|
538
|
+
() => undefined,
|
|
539
|
+
() => undefined,
|
|
540
|
+
);
|
|
541
|
+
return transaction;
|
|
542
|
+
}
|
|
497
543
|
/**
|
|
498
544
|
* Set an agent model override while keeping any live runtime override aligned.
|
|
499
545
|
*
|
|
@@ -824,15 +870,10 @@ export class Settings {
|
|
|
824
870
|
#queueSave(): void {
|
|
825
871
|
if (!this.#persist || !this.#configPath) return;
|
|
826
872
|
|
|
827
|
-
|
|
828
|
-
if (this.#saveTimer) {
|
|
829
|
-
clearTimeout(this.#saveTimer);
|
|
830
|
-
}
|
|
873
|
+
if (this.#saveTimer) clearTimeout(this.#saveTimer);
|
|
831
874
|
this.#saveTimer = setTimeout(() => {
|
|
832
875
|
this.#saveTimer = undefined;
|
|
833
|
-
this.#saveNow()
|
|
834
|
-
logger.warn("Settings: background save failed", { error: String(err) });
|
|
835
|
-
});
|
|
876
|
+
void this.#saveNow();
|
|
836
877
|
}, 100);
|
|
837
878
|
}
|
|
838
879
|
|
|
@@ -840,30 +881,40 @@ export class Settings {
|
|
|
840
881
|
if (!this.#persist || !this.#configPath || this.#modified.size === 0) return;
|
|
841
882
|
|
|
842
883
|
const configPath = this.#configPath;
|
|
843
|
-
const
|
|
844
|
-
this.#modified.clear();
|
|
884
|
+
const patches = [...this.#modified.values()];
|
|
845
885
|
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
// Re-read to preserve external changes
|
|
886
|
+
const save = this.#saveTail.then(() =>
|
|
887
|
+
withFileLock(configPath, async () => {
|
|
849
888
|
const current = await this.#loadYaml(configPath);
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
889
|
+
for (const patch of patches) {
|
|
890
|
+
setByPath(current, patch.path.split("."), patch.value);
|
|
891
|
+
}
|
|
892
|
+
await Bun.write(configPath, YAML.stringify(current, null, 2));
|
|
893
|
+
for (const patch of patches) {
|
|
894
|
+
if (this.#modified.get(patch.path)?.generation === patch.generation) {
|
|
895
|
+
this.#modified.delete(patch.path);
|
|
896
|
+
}
|
|
856
897
|
}
|
|
857
|
-
|
|
858
|
-
// Update our global with any external changes we preserved
|
|
859
898
|
this.#global = current;
|
|
860
|
-
|
|
861
|
-
|
|
899
|
+
for (const patch of this.#modified.values()) {
|
|
900
|
+
setByPath(this.#global, patch.path.split("."), structuredClone(patch.value));
|
|
901
|
+
}
|
|
902
|
+
}),
|
|
903
|
+
);
|
|
904
|
+
this.#saveTail = save.then(
|
|
905
|
+
() => undefined,
|
|
906
|
+
() => undefined,
|
|
907
|
+
);
|
|
908
|
+
|
|
909
|
+
try {
|
|
910
|
+
await save;
|
|
862
911
|
} catch (error) {
|
|
863
912
|
logger.warn("Settings: save failed", { error: String(error) });
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
913
|
+
for (const patch of patches) {
|
|
914
|
+
const currentPatch = this.#modified.get(patch.path);
|
|
915
|
+
if (currentPatch?.generation === patch.generation) {
|
|
916
|
+
this.#modified.set(patch.path, patch);
|
|
917
|
+
}
|
|
867
918
|
}
|
|
868
919
|
if (options.throwOnError) {
|
|
869
920
|
this.#rebuildMerged();
|
|
@@ -12,6 +12,7 @@ export const COORDINATOR_MCP_TOOL_NAMES = [
|
|
|
12
12
|
"gjc_coordinator_watch_events",
|
|
13
13
|
"gjc_coordinator_register_session",
|
|
14
14
|
"gjc_coordinator_start_session",
|
|
15
|
+
"gjc_coordinator_stop_session",
|
|
15
16
|
"gjc_coordinator_send_prompt",
|
|
16
17
|
"gjc_coordinator_submit_question_answer",
|
|
17
18
|
"gjc_coordinator_read_turn",
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { coordinatorMcpStateRoot, gjcRoot } from "../gjc-runtime/session-layout";
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_SESSION_IDLE_TTL_MS,
|
|
6
|
+
DEFAULT_SESSION_SWEEP_INTERVAL_MS,
|
|
7
|
+
MIN_SESSION_IDLE_TTL_MS,
|
|
8
|
+
MIN_SESSION_SWEEP_INTERVAL_MS,
|
|
9
|
+
} from "./session-reaper";
|
|
4
10
|
|
|
5
11
|
export type CoordinatorMutationClass = "sessions" | "questions" | "reports";
|
|
6
12
|
|
|
@@ -16,6 +22,9 @@ export interface CoordinatorMcpConfig {
|
|
|
16
22
|
namespace: CoordinatorNamespace;
|
|
17
23
|
stateRoot: string;
|
|
18
24
|
sessionCommand: string | null;
|
|
25
|
+
sessionIdleTtlMs: number;
|
|
26
|
+
sessionSweepIntervalMs: number;
|
|
27
|
+
forceStopEnabled: boolean;
|
|
19
28
|
}
|
|
20
29
|
|
|
21
30
|
export interface CoordinatorMutationRequest {
|
|
@@ -32,6 +41,16 @@ const LEGACY_MUTATION_CLASS_ALIASES = new Map<string, CoordinatorMutationClass>(
|
|
|
32
41
|
["report", "reports"],
|
|
33
42
|
]);
|
|
34
43
|
|
|
44
|
+
function parsePositiveIntMs(value: string | undefined, fallback: number, floor: number): number {
|
|
45
|
+
const parsed = Number.parseInt((value ?? "").trim(), 10);
|
|
46
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
|
|
47
|
+
return Math.max(floor, parsed);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function parseBool(value: string | undefined): boolean {
|
|
51
|
+
return /^(1|true|yes|on)$/i.test((value ?? "").trim());
|
|
52
|
+
}
|
|
53
|
+
|
|
35
54
|
function parseList(value: string | undefined): string[] {
|
|
36
55
|
return (value ?? "")
|
|
37
56
|
.split(/[\n,;:]+/)
|
|
@@ -98,6 +117,17 @@ export function buildCoordinatorMcpConfig(env: NodeJS.ProcessEnv = process.env):
|
|
|
98
117
|
},
|
|
99
118
|
stateRoot: path.resolve(stateRoot),
|
|
100
119
|
sessionCommand: env.GJC_COORDINATOR_MCP_SESSION_COMMAND?.trim() || null,
|
|
120
|
+
sessionIdleTtlMs: parsePositiveIntMs(
|
|
121
|
+
env.GJC_COORDINATOR_MCP_SESSION_IDLE_TTL_MS,
|
|
122
|
+
DEFAULT_SESSION_IDLE_TTL_MS,
|
|
123
|
+
MIN_SESSION_IDLE_TTL_MS,
|
|
124
|
+
),
|
|
125
|
+
sessionSweepIntervalMs: parsePositiveIntMs(
|
|
126
|
+
env.GJC_COORDINATOR_MCP_SESSION_SWEEP_INTERVAL_MS,
|
|
127
|
+
DEFAULT_SESSION_SWEEP_INTERVAL_MS,
|
|
128
|
+
MIN_SESSION_SWEEP_INTERVAL_MS,
|
|
129
|
+
),
|
|
130
|
+
forceStopEnabled: parseBool(env.GJC_COORDINATOR_MCP_FORCE_STOP),
|
|
101
131
|
};
|
|
102
132
|
}
|
|
103
133
|
|