@songsid/agend 2.1.7-beta.6 → 2.1.7-beta.8
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/dist/backend/codex-session-lookup.d.ts +31 -2
- package/dist/backend/codex-session-lookup.js +105 -8
- package/dist/backend/codex-session-lookup.js.map +1 -1
- package/dist/backend/codex.d.ts +8 -0
- package/dist/backend/codex.js +106 -0
- package/dist/backend/codex.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +11 -0
- package/dist/channel/adapters/discord.js +16 -2
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/types.d.ts +2 -0
- package/dist/config-validator.js +41 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.d.ts +3 -0
- package/dist/daemon.js +8 -0
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-manager.d.ts +59 -7
- package/dist/fleet-manager.js +167 -59
- package/dist/fleet-manager.js.map +1 -1
- package/dist/instance-config-impact.js +5 -0
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +10 -2
- package/dist/instance-lifecycle.js +30 -21
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.d.ts +11 -0
- package/dist/instructions.js +16 -1
- package/dist/instructions.js.map +1 -1
- package/dist/outbound-schemas.d.ts +1 -1
- package/dist/settings-api.d.ts +17 -0
- package/dist/settings-api.js +62 -1
- package/dist/settings-api.js.map +1 -1
- package/dist/status-emojis.d.ts +118 -0
- package/dist/status-emojis.js +206 -0
- package/dist/status-emojis.js.map +1 -0
- package/dist/types.d.ts +6 -0
- package/dist/ui/settings.html +168 -6
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Database from "better-sqlite3";
|
|
2
2
|
/** Columns the lookup depends on; any missing one means "schema unreadable". */
|
|
3
|
-
export declare const CODEX_THREAD_COLUMNS: readonly ["id", "cwd", "source", "archived", "has_user_event", "recency_at_ms", "updated_at_ms"];
|
|
3
|
+
export declare const CODEX_THREAD_COLUMNS: readonly ["id", "cwd", "source", "archived", "has_user_event", "first_user_message", "rollout_path", "recency_at_ms", "updated_at_ms"];
|
|
4
4
|
export type ExactCwdSession = {
|
|
5
5
|
kind: "found";
|
|
6
6
|
id: string;
|
|
@@ -36,8 +36,37 @@ export declare function openCodexStateReadonly(path: string): Database.Database;
|
|
|
36
36
|
* Newest interactive thread recorded for exactly this directory. Exact
|
|
37
37
|
* equality only: a prefix or LIKE match would take a subdirectory's (or a
|
|
38
38
|
* similarly named sibling's) session.
|
|
39
|
+
*
|
|
40
|
+
* "Has anything in it" was checked against real Codex data (#1017), not
|
|
41
|
+
* inferred from a column name:
|
|
42
|
+
* - `has_user_event` does NOT mean that. Real 0.157 sessions with a first
|
|
43
|
+
* message and millions of tokens carry 0. Filtering on it matched no real
|
|
44
|
+
* session, so every restart silently started a new conversation.
|
|
45
|
+
* - `tokens_used > 0` drops real sessions too: a provider that reports no
|
|
46
|
+
* token usage leaves it at 0, as does a restart during the first turn.
|
|
47
|
+
* - A non-empty `first_user_message` proves content, but an empty one does
|
|
48
|
+
* not prove the opposite: a real 38-turn session (0.156.1) and `/goal`-first
|
|
49
|
+
* sessions (openai/codex#28423) have empty list metadata. For those the
|
|
50
|
+
* rollout decides whether a turn ever ran. A thread nobody used (e.g. an
|
|
51
|
+
* untouched fork) holds only `session_meta`/`thread_settings_applied`; any
|
|
52
|
+
* turn — however its first input was delivered, including hidden goal
|
|
53
|
+
* context — writes `task_started`, a `turn_context` or a `response_item`.
|
|
54
|
+
* tests/fixtures/codex-real-threads.json holds the real rows behind this.
|
|
39
55
|
*/
|
|
40
|
-
export declare function findExactCwdCodexSession(stateDbPath: string, workingDirectory: string, open?: (path: string) => Database.Database): ExactCwdSession;
|
|
56
|
+
export declare function findExactCwdCodexSession(stateDbPath: string, workingDirectory: string, open?: (path: string) => Database.Database, rolloutHasTurn?: (path: string) => boolean): ExactCwdSession;
|
|
57
|
+
/**
|
|
58
|
+
* Whether a Codex rollout (JSONL) may hold a conversation. Read-only, in
|
|
59
|
+
* chunks, stopping at the first entry that is not part of an untouched thread
|
|
60
|
+
* — in real rollouts a turn entry follows the ~22 KB session_meta line.
|
|
61
|
+
*
|
|
62
|
+
* "No" needs proof: the file has the known untouched shape and nothing else
|
|
63
|
+
* (`session_meta` + `thread_settings_applied`). Every other entry — a turn,
|
|
64
|
+
* an entry type from a newer Codex, a line this code cannot parse — counts
|
|
65
|
+
* as yes: resuming a doubtful thread fails loudly, skipping a real one
|
|
66
|
+
* silently loses the conversation. A missing or unreadable rollout cannot be
|
|
67
|
+
* resumed, so it counts as no.
|
|
68
|
+
*/
|
|
69
|
+
export declare function rolloutRecordsTurn(path: string): boolean;
|
|
41
70
|
/**
|
|
42
71
|
* The directory's git common dir: a string for a repo, null for "not a git
|
|
43
72
|
* repository", "unknown" for anything else (timeout, git missing, …).
|
|
@@ -12,12 +12,13 @@
|
|
|
12
12
|
* docs/design/984-codex-exact-cwd-resume.zh-TW.md.
|
|
13
13
|
*/
|
|
14
14
|
import { execFileSync } from "node:child_process";
|
|
15
|
-
import { realpathSync } from "node:fs";
|
|
15
|
+
import { closeSync, openSync, readSync, realpathSync } from "node:fs";
|
|
16
16
|
import { resolve } from "node:path";
|
|
17
|
+
import { StringDecoder } from "node:string_decoder";
|
|
17
18
|
import Database from "better-sqlite3";
|
|
18
19
|
/** Columns the lookup depends on; any missing one means "schema unreadable". */
|
|
19
20
|
export const CODEX_THREAD_COLUMNS = [
|
|
20
|
-
"id", "cwd", "source", "archived", "has_user_event", "recency_at_ms", "updated_at_ms",
|
|
21
|
+
"id", "cwd", "source", "archived", "has_user_event", "first_user_message", "rollout_path", "recency_at_ms", "updated_at_ms",
|
|
21
22
|
];
|
|
22
23
|
const SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
23
24
|
/**
|
|
@@ -41,8 +42,24 @@ function cwdCandidates(workingDirectory) {
|
|
|
41
42
|
* Newest interactive thread recorded for exactly this directory. Exact
|
|
42
43
|
* equality only: a prefix or LIKE match would take a subdirectory's (or a
|
|
43
44
|
* similarly named sibling's) session.
|
|
45
|
+
*
|
|
46
|
+
* "Has anything in it" was checked against real Codex data (#1017), not
|
|
47
|
+
* inferred from a column name:
|
|
48
|
+
* - `has_user_event` does NOT mean that. Real 0.157 sessions with a first
|
|
49
|
+
* message and millions of tokens carry 0. Filtering on it matched no real
|
|
50
|
+
* session, so every restart silently started a new conversation.
|
|
51
|
+
* - `tokens_used > 0` drops real sessions too: a provider that reports no
|
|
52
|
+
* token usage leaves it at 0, as does a restart during the first turn.
|
|
53
|
+
* - A non-empty `first_user_message` proves content, but an empty one does
|
|
54
|
+
* not prove the opposite: a real 38-turn session (0.156.1) and `/goal`-first
|
|
55
|
+
* sessions (openai/codex#28423) have empty list metadata. For those the
|
|
56
|
+
* rollout decides whether a turn ever ran. A thread nobody used (e.g. an
|
|
57
|
+
* untouched fork) holds only `session_meta`/`thread_settings_applied`; any
|
|
58
|
+
* turn — however its first input was delivered, including hidden goal
|
|
59
|
+
* context — writes `task_started`, a `turn_context` or a `response_item`.
|
|
60
|
+
* tests/fixtures/codex-real-threads.json holds the real rows behind this.
|
|
44
61
|
*/
|
|
45
|
-
export function findExactCwdCodexSession(stateDbPath, workingDirectory, open = openCodexStateReadonly) {
|
|
62
|
+
export function findExactCwdCodexSession(stateDbPath, workingDirectory, open = openCodexStateReadonly, rolloutHasTurn = rolloutRecordsTurn) {
|
|
46
63
|
let db = null;
|
|
47
64
|
try {
|
|
48
65
|
db = open(stateDbPath);
|
|
@@ -51,15 +68,27 @@ export function findExactCwdCodexSession(stateDbPath, workingDirectory, open = o
|
|
|
51
68
|
if (missing.length > 0)
|
|
52
69
|
return { kind: "unreadable", reason: `threads schema missing: ${missing.join(", ")}` };
|
|
53
70
|
const [first, second = first] = cwdCandidates(workingDirectory);
|
|
54
|
-
|
|
55
|
-
|
|
71
|
+
// Every thread of this directory, newest first, until one has content —
|
|
72
|
+
// no page limit, so a run of empty threads cannot hide an older session.
|
|
73
|
+
const rows = db.prepare(`
|
|
74
|
+
SELECT id, has_user_event, first_user_message, rollout_path FROM threads
|
|
56
75
|
WHERE cwd IN (?, ?)
|
|
57
76
|
AND source = 'cli'
|
|
58
77
|
AND archived = 0
|
|
59
|
-
AND has_user_event = 1
|
|
60
78
|
ORDER BY recency_at_ms DESC, updated_at_ms DESC, id DESC
|
|
61
|
-
|
|
62
|
-
|
|
79
|
+
`).iterate(first, second);
|
|
80
|
+
let row;
|
|
81
|
+
for (const r of rows) {
|
|
82
|
+
// Any sign of content is enough; only a thread with none is skipped.
|
|
83
|
+
// has_user_event = 1 is what the #984 lookup accepted, so keeping it
|
|
84
|
+
// means this lookup never resumes less than that one did.
|
|
85
|
+
if (r.has_user_event === 1
|
|
86
|
+
|| (typeof r.first_user_message === "string" && r.first_user_message !== "")
|
|
87
|
+
|| (typeof r.rollout_path === "string" && rolloutHasTurn(r.rollout_path))) {
|
|
88
|
+
row = r;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
63
92
|
if (!row)
|
|
64
93
|
return { kind: "none" };
|
|
65
94
|
// A malformed id means the schema no longer means what we think it does.
|
|
@@ -79,6 +108,74 @@ export function findExactCwdCodexSession(stateDbPath, workingDirectory, open = o
|
|
|
79
108
|
catch { /* already closed */ }
|
|
80
109
|
}
|
|
81
110
|
}
|
|
111
|
+
/** Rollouts are read in chunks of this size until a turn entry or the end. */
|
|
112
|
+
const ROLLOUT_CHUNK_BYTES = 256 * 1024;
|
|
113
|
+
/**
|
|
114
|
+
* The entries of a thread nobody used. Real untouched threads (e.g. a fork)
|
|
115
|
+
* hold exactly `session_meta` and `event_msg:thread_settings_applied`.
|
|
116
|
+
*/
|
|
117
|
+
function isUntouchedThreadEntry(entry) {
|
|
118
|
+
return entry.type === "session_meta"
|
|
119
|
+
|| (entry.type === "event_msg" && entry.payload?.type === "thread_settings_applied");
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Whether a Codex rollout (JSONL) may hold a conversation. Read-only, in
|
|
123
|
+
* chunks, stopping at the first entry that is not part of an untouched thread
|
|
124
|
+
* — in real rollouts a turn entry follows the ~22 KB session_meta line.
|
|
125
|
+
*
|
|
126
|
+
* "No" needs proof: the file has the known untouched shape and nothing else
|
|
127
|
+
* (`session_meta` + `thread_settings_applied`). Every other entry — a turn,
|
|
128
|
+
* an entry type from a newer Codex, a line this code cannot parse — counts
|
|
129
|
+
* as yes: resuming a doubtful thread fails loudly, skipping a real one
|
|
130
|
+
* silently loses the conversation. A missing or unreadable rollout cannot be
|
|
131
|
+
* resumed, so it counts as no.
|
|
132
|
+
*/
|
|
133
|
+
export function rolloutRecordsTurn(path) {
|
|
134
|
+
let fd = null;
|
|
135
|
+
try {
|
|
136
|
+
fd = openSync(path, "r");
|
|
137
|
+
const buf = Buffer.alloc(ROLLOUT_CHUNK_BYTES);
|
|
138
|
+
// A multi-byte character may straddle two chunks; the decoder holds it.
|
|
139
|
+
const decoder = new StringDecoder("utf8");
|
|
140
|
+
let pending = "";
|
|
141
|
+
let position = 0;
|
|
142
|
+
let recognised = false;
|
|
143
|
+
for (;;) {
|
|
144
|
+
const read = readSync(fd, buf, 0, ROLLOUT_CHUNK_BYTES, position);
|
|
145
|
+
position += read;
|
|
146
|
+
const text = pending + (read === 0 ? decoder.end() : decoder.write(buf.subarray(0, read)));
|
|
147
|
+
const lines = text.split("\n");
|
|
148
|
+
pending = read === 0 ? "" : lines.pop() ?? "";
|
|
149
|
+
for (const line of lines) {
|
|
150
|
+
if (!line.trim())
|
|
151
|
+
continue;
|
|
152
|
+
let entry;
|
|
153
|
+
try {
|
|
154
|
+
entry = JSON.parse(line);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return true;
|
|
158
|
+
} // unparseable: not provably empty
|
|
159
|
+
if (!isUntouchedThreadEntry(entry))
|
|
160
|
+
return true;
|
|
161
|
+
if (entry.type === "session_meta")
|
|
162
|
+
recognised = true;
|
|
163
|
+
}
|
|
164
|
+
if (read === 0)
|
|
165
|
+
return !recognised;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
if (fd !== null)
|
|
173
|
+
try {
|
|
174
|
+
closeSync(fd);
|
|
175
|
+
}
|
|
176
|
+
catch { /* already closed */ }
|
|
177
|
+
}
|
|
178
|
+
}
|
|
82
179
|
export const defaultGitCommonDir = dir => {
|
|
83
180
|
try {
|
|
84
181
|
return execFileSync("git", ["-C", dir, "rev-parse", "--path-format=absolute", "--git-common-dir"], {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codex-session-lookup.js","sourceRoot":"","sources":["../../src/backend/codex-session-lookup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"codex-session-lookup.js","sourceRoot":"","sources":["../../src/backend/codex-session-lookup.ts"],"names":[],"mappings":"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"}
|
package/dist/backend/codex.d.ts
CHANGED
|
@@ -113,6 +113,14 @@ export declare class CodexBackend implements CliBackend {
|
|
|
113
113
|
* rather than preserved. TOML rejects duplicate keys, so replace in place.
|
|
114
114
|
*/
|
|
115
115
|
private disableStartupUpdateCheck;
|
|
116
|
+
/**
|
|
117
|
+
* Suppress Codex's rate-limit model-switch reminder for unattended instances.
|
|
118
|
+
* Codex documents this as `notice.hide_rate_limit_model_nudge` (a boolean in
|
|
119
|
+
* the `[notice]` table). This changes only the per-instance config; the
|
|
120
|
+
* runtime holdOnly dialog remains as defense-in-depth for older or changed
|
|
121
|
+
* CLI behavior. No model option is selected automatically.
|
|
122
|
+
*/
|
|
123
|
+
private hideRateLimitModelNudge;
|
|
116
124
|
/**
|
|
117
125
|
* Ensure Codex's TUI status line shows context usage: the Context item is
|
|
118
126
|
* both what /ctx scrapes and the footer AgEnD's readiness proofs rely on.
|
package/dist/backend/codex.js
CHANGED
|
@@ -109,6 +109,87 @@ function tomlTable(value) {
|
|
|
109
109
|
return value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date)
|
|
110
110
|
? value : null;
|
|
111
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Set one boolean inside a TOML table while leaving unrelated config text
|
|
114
|
+
* intact. Existing tables keep their layout; when the table isn't declared,
|
|
115
|
+
* use the equivalent dotted key in the root table. Table headers embedded in
|
|
116
|
+
* multiline strings are ignored.
|
|
117
|
+
*/
|
|
118
|
+
function setTomlTableBoolean(content, table, key) {
|
|
119
|
+
const lines = content.split(/(?<=\n)/);
|
|
120
|
+
let multiline = null;
|
|
121
|
+
const headers = [];
|
|
122
|
+
for (let index = 0; index < lines.length; index++) {
|
|
123
|
+
const line = lines[index];
|
|
124
|
+
if (!multiline) {
|
|
125
|
+
const header = line.match(/^\s*(?:\[([^\[\]]+)\]|\[\[([^\[\]]+)\]\])\s*(?:#.*)?(?:\r?\n)?$/);
|
|
126
|
+
if (header)
|
|
127
|
+
headers.push({ index, name: (header[1] ?? header[2]).trim(), isArray: header[2] !== undefined });
|
|
128
|
+
}
|
|
129
|
+
for (const delimiter of ['"""', "'''"]) {
|
|
130
|
+
if (multiline && multiline !== delimiter)
|
|
131
|
+
continue;
|
|
132
|
+
let count = 0;
|
|
133
|
+
let pos = 0;
|
|
134
|
+
while ((pos = line.indexOf(delimiter, pos)) !== -1) {
|
|
135
|
+
if (delimiter === "'''" || pos === 0 || line[pos - 1] !== "\\")
|
|
136
|
+
count++;
|
|
137
|
+
pos += delimiter.length;
|
|
138
|
+
}
|
|
139
|
+
if (count % 2 === 1)
|
|
140
|
+
multiline = multiline === delimiter ? null : delimiter;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const assignment = new RegExp(`^(\\s*${key}\\s*=\\s*)(?:true|false)(\\s*(?:#.*)?)(\\r?\\n?)$`);
|
|
144
|
+
const tableIndex = headers.findIndex(header => header.name === table && !header.isArray);
|
|
145
|
+
if (tableIndex >= 0) {
|
|
146
|
+
const start = headers[tableIndex].index + 1;
|
|
147
|
+
const end = headers[tableIndex + 1]?.index ?? lines.length;
|
|
148
|
+
for (let index = start; index < end; index++) {
|
|
149
|
+
const existing = lines[index].match(assignment);
|
|
150
|
+
if (existing) {
|
|
151
|
+
lines[index] = `${existing[1]}true${existing[2]}${existing[3]}`;
|
|
152
|
+
return lines.join("");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (end > 0 && !lines[end - 1].endsWith("\n"))
|
|
156
|
+
lines[end - 1] += "\n";
|
|
157
|
+
lines.splice(end, 0, `${key} = true\n`);
|
|
158
|
+
return lines.join("");
|
|
159
|
+
}
|
|
160
|
+
// Inline tables cannot be extended with dotted keys, so update the simple
|
|
161
|
+
// root-level `notice = { ... }` form in place when it is present.
|
|
162
|
+
const rootEnd = headers[0]?.index ?? lines.length;
|
|
163
|
+
const dottedAssignment = new RegExp(`^(\\s*${table}\\s*\\.\\s*${key}\\s*=\\s*)(?:true|false)(\\s*(?:#.*)?)(\\r?\\n?)$`);
|
|
164
|
+
for (let index = 0; index < rootEnd; index++) {
|
|
165
|
+
const existing = lines[index].match(dottedAssignment);
|
|
166
|
+
if (existing) {
|
|
167
|
+
lines[index] = `${existing[1]}true${existing[2]}${existing[3]}`;
|
|
168
|
+
return lines.join("");
|
|
169
|
+
}
|
|
170
|
+
const inline = lines[index].match(new RegExp(`^(\\s*${table}\\s*=\\s*\\{)(.*)(\\})(\\s*(?:#.*)?)(\\r?\\n?)$`));
|
|
171
|
+
if (inline) {
|
|
172
|
+
const option = new RegExp(`(${key}\\s*=\\s*)(?:true|false)`);
|
|
173
|
+
const body = option.test(inline[2])
|
|
174
|
+
? inline[2].replace(option, "$1true")
|
|
175
|
+
: `${inline[2].trim().length > 0 ? `${inline[2]}, ` : inline[2]}${key} = true`;
|
|
176
|
+
lines[index] = `${inline[1]}${body}${inline[3]}${inline[4]}${inline[5]}`;
|
|
177
|
+
return lines.join("");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// A dotted assignment is valid in the root table and can extend an implicit
|
|
181
|
+
// parent table created by `[notice.child]` without duplicating `[notice]`.
|
|
182
|
+
const line = `${table}.${key} = true\n`;
|
|
183
|
+
if (rootEnd === 0)
|
|
184
|
+
lines.unshift(line);
|
|
185
|
+
else if (lines[rootEnd - 1].endsWith("\n"))
|
|
186
|
+
lines.splice(rootEnd, 0, line);
|
|
187
|
+
else {
|
|
188
|
+
lines[rootEnd - 1] += "\n";
|
|
189
|
+
lines.splice(rootEnd, 0, line);
|
|
190
|
+
}
|
|
191
|
+
return lines.join("");
|
|
192
|
+
}
|
|
112
193
|
const CODEX_CONTEXT_STATUS_ITEM_RE = /^context-(?:remaining|usage|used)$/;
|
|
113
194
|
/** The TUI's effective status_line items: null when unset, "invalid" when unusable. */
|
|
114
195
|
function effectiveTuiStatusLine(content) {
|
|
@@ -867,6 +948,7 @@ export class CodexBackend {
|
|
|
867
948
|
atomicWritePrivate(join(this.isolatedCodexHome, "config.toml"), content);
|
|
868
949
|
this.enableContextStatusLine();
|
|
869
950
|
this.disableStartupUpdateCheck();
|
|
951
|
+
this.hideRateLimitModelNudge();
|
|
870
952
|
// Write fleet instructions into AGENTS.md (additive via marker block)
|
|
871
953
|
if (config.instructions) {
|
|
872
954
|
try {
|
|
@@ -930,6 +1012,30 @@ export class CodexBackend {
|
|
|
930
1012
|
}
|
|
931
1013
|
catch { /* best effort */ }
|
|
932
1014
|
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Suppress Codex's rate-limit model-switch reminder for unattended instances.
|
|
1017
|
+
* Codex documents this as `notice.hide_rate_limit_model_nudge` (a boolean in
|
|
1018
|
+
* the `[notice]` table). This changes only the per-instance config; the
|
|
1019
|
+
* runtime holdOnly dialog remains as defense-in-depth for older or changed
|
|
1020
|
+
* CLI behavior. No model option is selected automatically.
|
|
1021
|
+
*/
|
|
1022
|
+
hideRateLimitModelNudge() {
|
|
1023
|
+
const configPath = join(this.isolatedCodexHome, "config.toml");
|
|
1024
|
+
let content = "";
|
|
1025
|
+
try {
|
|
1026
|
+
content = readFileSync(configPath, "utf-8");
|
|
1027
|
+
}
|
|
1028
|
+
catch {
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
const updated = setTomlTableBoolean(content, "notice", "hide_rate_limit_model_nudge");
|
|
1032
|
+
if (updated !== content) {
|
|
1033
|
+
try {
|
|
1034
|
+
atomicWritePrivate(configPath, updated);
|
|
1035
|
+
}
|
|
1036
|
+
catch { /* best effort */ }
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
933
1039
|
/**
|
|
934
1040
|
* Ensure Codex's TUI status line shows context usage: the Context item is
|
|
935
1041
|
* both what /ctx scrapes and the footer AgEnD's readiness proofs rely on.
|