@gajae-code/coding-agent 0.12.16 → 0.12.18
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 +10 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +5 -2
- package/dist/types/sdk/model-profile-model.d.ts +1 -10
- package/dist/types/sdk/model-profile-namespace.d.ts +8 -0
- package/package.json +8 -8
- package/src/modes/acp/acp-agent.ts +1 -1
- package/src/sdk/bus/telegram-daemon-contract.ts +5 -2
- package/src/sdk/bus/topic-registry.ts +8 -0
- package/src/sdk/host/query/revision-store.ts +26 -4
- package/src/sdk/model-profile-model.ts +2 -10
- package/src/sdk/model-profile-namespace.ts +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.12.18] - 2026-08-08
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- SDK snapshot spill writes now retry once with a fresh temporary file when Bun reports `EBADF` during write or `fsync` under heavily contended descriptor teardown, and no longer fail when `close()` reports `EBADF` after a successful write and sync. The atomic writer removes the abandoned attempt before retrying, preserves primary I/O failures, accepts only the proven already-closed close case, and keeps every non-`EBADF` error or repeated descriptor failure fatal.
|
|
10
|
+
- ACP now imports the synthetic model-profile provider namespace from a dependency-free SDK constant module, keeping the machine entrypoint out of model-registry, session-host, MCP-manager, and extension-runner closures enforced by `check:runtime`.
|
|
11
|
+
|
|
12
|
+
## [0.12.17] - 2026-08-08
|
|
13
|
+
|
|
5
14
|
## [0.12.16] - 2026-08-08
|
|
6
15
|
|
|
7
16
|
### Added
|
|
@@ -39,6 +48,7 @@
|
|
|
39
48
|
- `sticky-viewport-showcase.test.ts` runs in ~53s instead of ~192s, cutting the slowest CI test shard from ~369s to ~167s locally. Each of its 17 cases spawned a `bun` subprocess that re-rendered all 20 showcase frames (~9.4s per capture, ~187s of the file's ~192s) purely to obtain a pristine bundle it then corrupted. The bundle is now captured once and handed out as filesystem copies; the one case that must observe mutated `GJC_STICKY_VIEWPORT_ORACLE_COMMIT` state still captures for itself. Isolation is load-bearing and verified: sharing the directory instead of copying it fails 9 cases.
|
|
40
49
|
|
|
41
50
|
### Fixed
|
|
51
|
+
- Telegram topic archival no longer corrupts the shared topic registry when an active topic first enters disconnect grace. `beginArchive` changed `authorityState` to `archive_pending` but retained `disconnectGraceExpiresAt`, a field the persisted-state parser permits only for `disconnect_grace`; one closed or stale session therefore made the entire otherwise-valid registry unreadable, after which every daemon scan reported `shared topic authority unavailable` and no new session could create a thread or replay a message. Every transition out of disconnect grace now clears the grace-only deadline before persistence. `DAEMON_GENERATION` bumped to 57.
|
|
42
52
|
|
|
43
53
|
- `notifications-topic-registry.test.ts` pins `DAEMON_GENERATION` at 54 after #3965 (was stale at 53; Dev CI run 31133356543).
|
|
44
54
|
- `smithery-env-trust.test.ts` no longer awaits hung probe pipes past a hard per-attempt deadline: minimal child env, `stdin: "ignore"`, SIGKILL + settled race, and up to 2 timeout-only retries. Fixes the inherited-config case that hit exactly 60001ms on Dev CI run 31133356543 after kill-at-45s left `Promise.all` on stdout/stderr unresolved. Assertions unchanged.
|
|
@@ -85,9 +85,12 @@ export declare const NOTIFICATION_PROTOCOL_VERSION = 3;
|
|
|
85
85
|
* of escaping to the process-level fatal handler, authority-failure throws
|
|
86
86
|
* preserve their underlying cause, and the compensation fence retry is bounded.
|
|
87
87
|
* Generation 56 moves exact unlink and process-incarnation authority behind
|
|
88
|
-
* lazy native bindings for the startup-cost cut (#3846).
|
|
88
|
+
* lazy native bindings for the startup-cost cut (#3846). Generation 57 clears
|
|
89
|
+
* disconnect-grace-only fields whenever a topic enters an archive state, so
|
|
90
|
+
* one stale session cannot make the shared registry unreadable and block every
|
|
91
|
+
* later session from creating a topic or replaying notifications.
|
|
89
92
|
*/
|
|
90
|
-
export declare const DAEMON_GENERATION =
|
|
93
|
+
export declare const DAEMON_GENERATION = 57;
|
|
91
94
|
/**
|
|
92
95
|
* Serving-compatibility boundary for daemon lifecycle requests. Epoch 5
|
|
93
96
|
* requires the complete generation-36 topic authority contract, so older
|
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
import type { ModelProfileDefinition } from "../config/model-profiles";
|
|
2
|
-
|
|
3
|
-
* Reserved logical provider namespace for the preset-as-model facade.
|
|
4
|
-
*
|
|
5
|
-
* `gajae-code/<profile>` is not a real API provider: rows under this namespace
|
|
6
|
-
* exist only in the SDK Q10 projection and `model.set` selection routes to a
|
|
7
|
-
* session-scoped model-profile activation transaction. The namespace is
|
|
8
|
-
* reserved so a user-defined `models.yml` provider of the same name cannot
|
|
9
|
-
* silently shadow the facade (see {@link syntheticNamespaceCollision}).
|
|
10
|
-
*/
|
|
11
|
-
export declare const SYNTHETIC_PROVIDER_ID = "gajae-code";
|
|
2
|
+
export { SYNTHETIC_PROVIDER_ID } from "./model-profile-namespace";
|
|
12
3
|
/**
|
|
13
4
|
* Display metadata used for synthetic rows whose profile default model cannot
|
|
14
5
|
* be resolved to a registered model. Mirrors the shared unknown-model
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reserved logical provider namespace for the preset-as-model facade.
|
|
3
|
+
*
|
|
4
|
+
* Kept dependency-free because machine entrypoints use the namespace when
|
|
5
|
+
* filtering projected models and must not pull the model registry/session host
|
|
6
|
+
* graph into their static import closure.
|
|
7
|
+
*/
|
|
8
|
+
export declare const SYNTHETIC_PROVIDER_ID = "gajae-code";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/coding-agent",
|
|
4
|
-
"version": "0.12.
|
|
4
|
+
"version": "0.12.18",
|
|
5
5
|
"description": "Gajae Code CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://gajae-code.com",
|
|
7
7
|
"author": "Yeachan-Heo and Gajae Code Contributors",
|
|
@@ -54,13 +54,13 @@
|
|
|
54
54
|
"@agentclientprotocol/sdk": "1.3.0",
|
|
55
55
|
"@babel/parser": "^7.29.3",
|
|
56
56
|
"@mozilla/readability": "^0.6.0",
|
|
57
|
-
"@gajae-code/stats": "0.12.
|
|
58
|
-
"@gajae-code/agent-core": "0.12.
|
|
59
|
-
"@gajae-code/ai": "0.12.
|
|
60
|
-
"@gajae-code/bridge-client": "0.12.
|
|
61
|
-
"@gajae-code/natives": "0.12.
|
|
62
|
-
"@gajae-code/tui": "0.12.
|
|
63
|
-
"@gajae-code/utils": "0.12.
|
|
57
|
+
"@gajae-code/stats": "0.12.18",
|
|
58
|
+
"@gajae-code/agent-core": "0.12.18",
|
|
59
|
+
"@gajae-code/ai": "0.12.18",
|
|
60
|
+
"@gajae-code/bridge-client": "0.12.18",
|
|
61
|
+
"@gajae-code/natives": "0.12.18",
|
|
62
|
+
"@gajae-code/tui": "0.12.18",
|
|
63
|
+
"@gajae-code/utils": "0.12.18",
|
|
64
64
|
"@puppeteer/browsers": "^2.13.0",
|
|
65
65
|
"@types/turndown": "5.0.6",
|
|
66
66
|
"@xterm/headless": "^6.0.0",
|
|
@@ -48,7 +48,7 @@ import { resolveAcpFinalText } from "../../sdk/acp/final-text";
|
|
|
48
48
|
import { ACP_MCP_LIFECYCLE_TIMEOUT_MS, type SessionLifecycleMcpServer } from "../../sdk/acp/mcp";
|
|
49
49
|
import { ensureBroker } from "../../sdk/broker/ensure";
|
|
50
50
|
import { readSdkBrokerDiscovery, SdkClient, SdkClientError } from "../../sdk/client";
|
|
51
|
-
import { SYNTHETIC_PROVIDER_ID } from "../../sdk/model-profile-
|
|
51
|
+
import { SYNTHETIC_PROVIDER_ID } from "../../sdk/model-profile-namespace";
|
|
52
52
|
import type { SdkPromptTerminalOutcome } from "../../sdk/prompt-status";
|
|
53
53
|
import {
|
|
54
54
|
buildToolCallStartUpdate,
|
|
@@ -87,9 +87,12 @@ export const NOTIFICATION_PROTOCOL_VERSION = 3;
|
|
|
87
87
|
* of escaping to the process-level fatal handler, authority-failure throws
|
|
88
88
|
* preserve their underlying cause, and the compensation fence retry is bounded.
|
|
89
89
|
* Generation 56 moves exact unlink and process-incarnation authority behind
|
|
90
|
-
* lazy native bindings for the startup-cost cut (#3846).
|
|
90
|
+
* lazy native bindings for the startup-cost cut (#3846). Generation 57 clears
|
|
91
|
+
* disconnect-grace-only fields whenever a topic enters an archive state, so
|
|
92
|
+
* one stale session cannot make the shared registry unreadable and block every
|
|
93
|
+
* later session from creating a topic or replaying notifications.
|
|
91
94
|
*/
|
|
92
|
-
export const DAEMON_GENERATION =
|
|
95
|
+
export const DAEMON_GENERATION = 57;
|
|
93
96
|
|
|
94
97
|
/**
|
|
95
98
|
* Serving-compatibility boundary for daemon lifecycle requests. Epoch 5
|
|
@@ -1202,11 +1202,14 @@ export class TopicRegistry {
|
|
|
1202
1202
|
authorityEpoch: deleteEpoch,
|
|
1203
1203
|
authorityState: "archive_pending",
|
|
1204
1204
|
});
|
|
1205
|
+
const restored = this.topics.get(snapshot.sessionId);
|
|
1206
|
+
if (restored) delete restored.disconnectGraceExpiresAt;
|
|
1205
1207
|
} else if (record.topicId !== snapshot.topicId) {
|
|
1206
1208
|
return false;
|
|
1207
1209
|
} else {
|
|
1208
1210
|
record.authorityEpoch = deleteEpoch;
|
|
1209
1211
|
record.authorityState = "archive_pending";
|
|
1212
|
+
delete record.disconnectGraceExpiresAt;
|
|
1210
1213
|
if (this.byTopic.get(record.topicId) === snapshot.sessionId) this.byTopic.delete(record.topicId);
|
|
1211
1214
|
}
|
|
1212
1215
|
this.epochs.set(snapshot.sessionId, deleteEpoch);
|
|
@@ -1230,6 +1233,7 @@ export class TopicRegistry {
|
|
|
1230
1233
|
if (record) {
|
|
1231
1234
|
record.authorityEpoch = Number.MAX_SAFE_INTEGER;
|
|
1232
1235
|
record.authorityState = "archive_exhausted";
|
|
1236
|
+
delete record.disconnectGraceExpiresAt;
|
|
1233
1237
|
if (this.byTopic.get(record.topicId) === sessionId) this.byTopic.delete(record.topicId);
|
|
1234
1238
|
}
|
|
1235
1239
|
return undefined;
|
|
@@ -1239,6 +1243,7 @@ export class TopicRegistry {
|
|
|
1239
1243
|
if (!record) return undefined;
|
|
1240
1244
|
record.authorityEpoch = epoch;
|
|
1241
1245
|
record.authorityState = "archive_pending";
|
|
1246
|
+
delete record.disconnectGraceExpiresAt;
|
|
1242
1247
|
if (hostId) record.archiveHostId = hostId;
|
|
1243
1248
|
record.archiveLeaseEpoch = epoch;
|
|
1244
1249
|
if (this.byTopic.get(record.topicId) === sessionId) this.byTopic.delete(record.topicId);
|
|
@@ -1368,6 +1373,7 @@ export class TopicRegistry {
|
|
|
1368
1373
|
)
|
|
1369
1374
|
return false;
|
|
1370
1375
|
record.authorityState = "inactive";
|
|
1376
|
+
delete record.disconnectGraceExpiresAt;
|
|
1371
1377
|
if (this.byTopic.get(record.topicId) === sessionId) this.byTopic.delete(record.topicId);
|
|
1372
1378
|
this.archiveJobs.delete(sessionId);
|
|
1373
1379
|
return true;
|
|
@@ -1383,6 +1389,7 @@ export class TopicRegistry {
|
|
|
1383
1389
|
record.authorityEpoch = epoch;
|
|
1384
1390
|
record.archiveLeaseEpoch = epoch;
|
|
1385
1391
|
record.authorityState = "archive_pending";
|
|
1392
|
+
delete record.disconnectGraceExpiresAt;
|
|
1386
1393
|
}
|
|
1387
1394
|
}
|
|
1388
1395
|
return (record.authorityState === "archive_pending" || record.authorityState === "archive_exhausted") &&
|
|
@@ -1420,6 +1427,7 @@ export class TopicRegistry {
|
|
|
1420
1427
|
...(exhausted ? { safeDiagnostic: "archive retry remains discoverable after retry budget" } : {}),
|
|
1421
1428
|
};
|
|
1422
1429
|
record.authorityState = "archive_pending";
|
|
1430
|
+
delete record.disconnectGraceExpiresAt;
|
|
1423
1431
|
this.archiveJobs.set(sessionId, job);
|
|
1424
1432
|
return job;
|
|
1425
1433
|
}
|
|
@@ -1084,16 +1084,38 @@ export class RevisionStore {
|
|
|
1084
1084
|
}
|
|
1085
1085
|
|
|
1086
1086
|
async #writeAtomic(file: string, data: Buffer): Promise<void> {
|
|
1087
|
-
|
|
1087
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
1088
|
+
const temporary = `${file}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`;
|
|
1089
|
+
try {
|
|
1090
|
+
await this.#writeAtomicAttempt(temporary, data);
|
|
1091
|
+
await chmod(temporary, 0o600);
|
|
1092
|
+
await rename(temporary, file);
|
|
1093
|
+
return;
|
|
1094
|
+
} catch (error) {
|
|
1095
|
+
await rm(temporary, { force: true }).catch(() => undefined);
|
|
1096
|
+
if ((error as NodeJS.ErrnoException).code !== "EBADF" || attempt === 1) throw error;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
async #writeAtomicAttempt(temporary: string, data: Buffer): Promise<void> {
|
|
1088
1102
|
const handle = await open(temporary, "w", 0o600);
|
|
1103
|
+
let failure: { error: unknown } | undefined;
|
|
1089
1104
|
try {
|
|
1090
1105
|
await handle.writeFile(data);
|
|
1091
1106
|
await handle.sync();
|
|
1092
|
-
}
|
|
1107
|
+
} catch (error) {
|
|
1108
|
+
failure = { error };
|
|
1109
|
+
}
|
|
1110
|
+
try {
|
|
1093
1111
|
await handle.close();
|
|
1112
|
+
} catch (error) {
|
|
1113
|
+
// Bun can report EBADF when concurrent descriptor teardown has already
|
|
1114
|
+
// closed a fully written and fsynced handle. Preserve every primary write
|
|
1115
|
+
// failure and keep all other close errors fatal.
|
|
1116
|
+
if ((error as NodeJS.ErrnoException).code !== "EBADF" && failure === undefined) failure = { error };
|
|
1094
1117
|
}
|
|
1095
|
-
|
|
1096
|
-
await rename(temporary, file);
|
|
1118
|
+
if (failure !== undefined) throw failure.error;
|
|
1097
1119
|
}
|
|
1098
1120
|
|
|
1099
1121
|
#retainSpill(revision: Revision): void {
|
|
@@ -6,17 +6,9 @@ import {
|
|
|
6
6
|
} from "../config/model-profile-contract";
|
|
7
7
|
import type { ModelProfileDefinition } from "../config/model-profiles";
|
|
8
8
|
import { isAuthenticated, kNoAuth } from "../config/model-registry";
|
|
9
|
+
import { SYNTHETIC_PROVIDER_ID } from "./model-profile-namespace";
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
* Reserved logical provider namespace for the preset-as-model facade.
|
|
12
|
-
*
|
|
13
|
-
* `gajae-code/<profile>` is not a real API provider: rows under this namespace
|
|
14
|
-
* exist only in the SDK Q10 projection and `model.set` selection routes to a
|
|
15
|
-
* session-scoped model-profile activation transaction. The namespace is
|
|
16
|
-
* reserved so a user-defined `models.yml` provider of the same name cannot
|
|
17
|
-
* silently shadow the facade (see {@link syntheticNamespaceCollision}).
|
|
18
|
-
*/
|
|
19
|
-
export const SYNTHETIC_PROVIDER_ID = "gajae-code";
|
|
11
|
+
export { SYNTHETIC_PROVIDER_ID } from "./model-profile-namespace";
|
|
20
12
|
|
|
21
13
|
/**
|
|
22
14
|
* Display metadata used for synthetic rows whose profile default model cannot
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reserved logical provider namespace for the preset-as-model facade.
|
|
3
|
+
*
|
|
4
|
+
* Kept dependency-free because machine entrypoints use the namespace when
|
|
5
|
+
* filtering projected models and must not pull the model registry/session host
|
|
6
|
+
* graph into their static import closure.
|
|
7
|
+
*/
|
|
8
|
+
export const SYNTHETIC_PROVIDER_ID = "gajae-code";
|