@khorsheed/dsh-local-agent-codex 0.1.0-rc.6

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.
@@ -0,0 +1,505 @@
1
+ /**
2
+ * Codex session records adapter: scans the scoped home's `sessions/`
3
+ * rollout files (one JSONL per session under `sessions/YYYY/MM/DD/`).
4
+ * The LISTING reads only the head of each file — the first `session_meta`
5
+ * line carries the id, cwd, and started-at timestamp it needs. The READ-BACK
6
+ * path ({@link codexRolloutRoundFacts}) instead locates one run's file (by
7
+ * thread id, working directory, or a time window) and reads that round's
8
+ * usage, model, and codex build out of it — the facts the exec `--json` wire
9
+ * either omits entirely (the model) or drops on a non-completed round (the
10
+ * usage). Files are treated as append-only with torn-tail tolerance, and
11
+ * contents are always untrusted input.
12
+ * @module @khorsheed/dsh-local-agent-codex/records
13
+ */
14
+ import { open, readFile, readdir } from 'node:fs/promises';
15
+ import { join, resolve } from 'node:path';
16
+ /** Rollout files live under this directory inside the scoped home. */
17
+ const SESSIONS_ROOT = 'sessions';
18
+ /** Bound on the head prefix read from one rollout file. */
19
+ const HEAD_BYTES = 64 * 1024;
20
+ /** Bound on the tail read from one rollout file for the usage scan. */
21
+ const TAIL_BYTES = 64 * 1024;
22
+ /** Bound on the full-file fallback read when the tail scan finds no token_count. */
23
+ const FULL_BYTES = 8 * 1024 * 1024;
24
+ /**
25
+ * How far back a candidate file's start may precede the run's start and
26
+ * still count as "this run" (clock skew + pre-spawn session creation slack).
27
+ */
28
+ const ROLLOUT_WINDOW_SLACK_MS = 5 * 60_000;
29
+ /** Extra future slack for the window end (file timestamps may skew ahead). */
30
+ const ROLLOUT_WINDOW_FUTURE_MS = 60_000;
31
+ /**
32
+ * Whether the scoped home holds usable Codex credentials: the `auth.json`
33
+ * file exists. Absent means the device-code flow has not completed.
34
+ * @param homeDir - the `codex` harness's scoped home.
35
+ * @returns true when credentials are present.
36
+ */
37
+ export async function codexAuthenticated(homeDir) {
38
+ try {
39
+ await readFile(join(homeDir, 'auth.json'));
40
+ return true;
41
+ }
42
+ catch (error) {
43
+ // No auth file yet: not authenticated.
44
+ if (error.code === 'ENOENT')
45
+ return false;
46
+ throw error;
47
+ }
48
+ }
49
+ /**
50
+ * Parse the first line of a rollout file for the listing fields. Unknown or
51
+ * malformed values yield an undefined record rather than failing the list.
52
+ * @param text - the raw rollout file content (only the head is read).
53
+ * @returns the record, or undefined when the head is not a session_meta line.
54
+ */
55
+ export function rolloutRecord(text) {
56
+ const line = text.split('\n')[0];
57
+ if (line === undefined)
58
+ return undefined;
59
+ let event;
60
+ try {
61
+ event = JSON.parse(line);
62
+ }
63
+ catch {
64
+ return undefined;
65
+ }
66
+ if (event.type !== 'session_meta' || typeof event.payload !== 'object' || event.payload === null) {
67
+ return undefined;
68
+ }
69
+ const meta = event.payload;
70
+ const sessionId = typeof meta.id === 'string' && meta.id !== ''
71
+ ? meta.id
72
+ : typeof meta.session_id === 'string' && meta.session_id !== ''
73
+ ? meta.session_id
74
+ : undefined;
75
+ if (sessionId === undefined)
76
+ return undefined;
77
+ if (typeof meta.cwd !== 'string')
78
+ return undefined;
79
+ const record = { id: sessionId, workDir: meta.cwd };
80
+ const startedAt = typeof meta.timestamp === 'string' ? Date.parse(meta.timestamp) : undefined;
81
+ if (startedAt !== undefined && Number.isFinite(startedAt))
82
+ record.startedAt = startedAt;
83
+ return record;
84
+ }
85
+ /**
86
+ * Read the scoped home's rollout files. The listing walks
87
+ * `sessions/YYYY/MM/DD/` and reads each `rollout-*.jsonl` head; a torn or
88
+ * malformed file is skipped rather than failing the whole list.
89
+ * @param homeDir - the `codex` harness's scoped home.
90
+ * @returns the parsed records, empty when no sessions exist yet.
91
+ */
92
+ export async function listCodexSessions(homeDir) {
93
+ const records = [];
94
+ let years;
95
+ try {
96
+ years = await readdir(join(homeDir, SESSIONS_ROOT));
97
+ }
98
+ catch (error) {
99
+ // No sessions root yet: nothing to list.
100
+ if (error.code === 'ENOENT')
101
+ return [];
102
+ throw error;
103
+ }
104
+ for (const year of years) {
105
+ if (!/^\d{4}$/.test(year))
106
+ continue;
107
+ let months;
108
+ try {
109
+ months = await readdir(join(homeDir, SESSIONS_ROOT, year));
110
+ }
111
+ catch {
112
+ continue;
113
+ }
114
+ for (const month of months) {
115
+ if (!/^\d{2}$/.test(month))
116
+ continue;
117
+ let days;
118
+ try {
119
+ days = await readdir(join(homeDir, SESSIONS_ROOT, year, month));
120
+ }
121
+ catch {
122
+ continue;
123
+ }
124
+ for (const day of days) {
125
+ if (!/^\d{2}$/.test(day))
126
+ continue;
127
+ let files;
128
+ try {
129
+ files = await readdir(join(homeDir, SESSIONS_ROOT, year, month, day));
130
+ }
131
+ catch {
132
+ continue;
133
+ }
134
+ for (const name of files) {
135
+ if (!name.startsWith('rollout-') || !name.endsWith('.jsonl'))
136
+ continue;
137
+ try {
138
+ // Only the head matters; read a bounded prefix rather than the
139
+ // whole (potentially long) event log.
140
+ const handle = await open(join(homeDir, SESSIONS_ROOT, year, month, day, name), 'r');
141
+ let text;
142
+ try {
143
+ const buffer = Buffer.alloc(HEAD_BYTES);
144
+ const { bytesRead } = await handle.read(buffer, 0, HEAD_BYTES, 0);
145
+ text = buffer.subarray(0, bytesRead).toString('utf8');
146
+ }
147
+ finally {
148
+ await handle.close();
149
+ }
150
+ const record = rolloutRecord(text);
151
+ if (record !== undefined)
152
+ records.push(record);
153
+ }
154
+ catch {
155
+ continue;
156
+ }
157
+ }
158
+ }
159
+ }
160
+ }
161
+ return records;
162
+ }
163
+ /**
164
+ * Map codex's token-count payload onto the shared usage contract — the
165
+ * SINGLE caliber for both the exec `turn.completed` usage and the rollout
166
+ * `token_count` recovery, so the two sources can never drift. Codex's
167
+ * `input_tokens` is the TOTAL input including cache hits (OpenAI-style
168
+ * accounting, confirmed against `total_tokens` in the rollout token_count),
169
+ * and `cached_input_tokens` is the cache-read subset — so the uncached
170
+ * bucket subtracts the cached portion to avoid double counting. There is no
171
+ * cache-write concept, so that bucket is omitted (the shared four-bucket
172
+ * contract: uncached input, output, cache read, cache write — codex fills
173
+ * the first three).
174
+ * @param usage - the raw codex usage object (`turn.completed`'s `usage` or a
175
+ * rollout `token_count` `info.last_token_usage`/`total_token_usage`).
176
+ * @returns the shared usage record.
177
+ */
178
+ export function usageFromCodex(usage) {
179
+ const raw = usage;
180
+ const input = Number(raw.input_tokens);
181
+ const cached = Number(raw.cached_input_tokens);
182
+ const output = Number(raw.output_tokens);
183
+ const uncached = Number.isFinite(input) && Number.isFinite(cached)
184
+ ? Math.max(0, input - cached)
185
+ : Number.isFinite(input)
186
+ ? input
187
+ : 0;
188
+ const usageRecord = {
189
+ inputTokens: uncached,
190
+ outputTokens: Number.isFinite(output) ? output : 0,
191
+ };
192
+ if (Number.isFinite(cached) && cached > 0)
193
+ usageRecord.cacheReadTokens = cached;
194
+ return usageRecord;
195
+ }
196
+ /**
197
+ * Scan rollout-file text for the LAST `token_count` event and map its usage
198
+ * onto the shared contract. A token_count entry rides an
199
+ * `{"type":"event_msg","payload":{"type":"token_count","info":{…}}}` line
200
+ * (verified against codex-cli 0.144.0 rollout files); the per-turn
201
+ * `info.last_token_usage` is the same caliber as `turn.completed`'s usage,
202
+ * with the session-wide `total_token_usage` as fallback. Torn or malformed
203
+ * lines are skipped; an absent event yields undefined.
204
+ * @param text - raw rollout file content (the tail scan reads a bounded suffix).
205
+ * @returns the mapped usage of the last token_count event, or undefined.
206
+ */
207
+ export function codexRolloutTokenUsage(text) {
208
+ let info;
209
+ for (const raw of text.split('\n')) {
210
+ const line = raw.trim();
211
+ if (line === '')
212
+ continue;
213
+ let event;
214
+ try {
215
+ event = JSON.parse(line);
216
+ }
217
+ catch {
218
+ continue;
219
+ }
220
+ if (event.type !== 'event_msg' || typeof event.payload !== 'object' || event.payload === null)
221
+ continue;
222
+ const payload = event.payload;
223
+ if (payload.type !== 'token_count')
224
+ continue;
225
+ if (typeof payload.info === 'object' && payload.info !== null)
226
+ info = payload.info;
227
+ }
228
+ if (info === undefined)
229
+ return undefined;
230
+ const entry = info;
231
+ const usage = entry.last_token_usage ?? entry.total_token_usage;
232
+ return usage === undefined ? undefined : usageFromCodex(usage);
233
+ }
234
+ /** Parse the ISO timestamp embedded in a `rollout-<ISO>-<uuid>.jsonl` name. */
235
+ function rolloutFilenameTimestamp(name) {
236
+ // `rollout-2026-07-04T19-58-19-019f2cfe….jsonl`: the time part uses dashes
237
+ // instead of colons; normalize before Date.parse.
238
+ const match = /^rollout-(\d{4}-\d{2}-\d{2}T\d{2})-(\d{2})-(\d{2})/.exec(name);
239
+ if (match === null)
240
+ return undefined;
241
+ const parsed = Date.parse(`${match[1]}:${match[2]}:${match[3]}`);
242
+ return Number.isFinite(parsed) ? parsed : undefined;
243
+ }
244
+ /**
245
+ * The codex build that wrote one rollout file, from its `session_meta` head
246
+ * (`payload.cli_version`, present since codex 0.14x). This is the AUTHORITY
247
+ * for the version a delegation round actually ran: it is codex's own record of
248
+ * itself, written by the process that served the round, so it cannot drift
249
+ * from the binary the way a later `--version` probe can.
250
+ * @param text - the raw rollout head.
251
+ * @returns the version, or undefined when the head names none.
252
+ */
253
+ export function rolloutCliVersion(text) {
254
+ const line = text.split('\n')[0];
255
+ if (line === undefined)
256
+ return undefined;
257
+ let event;
258
+ try {
259
+ event = JSON.parse(line);
260
+ }
261
+ catch {
262
+ return undefined;
263
+ }
264
+ if (event.type !== 'session_meta' || typeof event.payload !== 'object' || event.payload === null) {
265
+ return undefined;
266
+ }
267
+ const version = event.payload.cli_version;
268
+ return typeof version === 'string' && version !== '' ? version : undefined;
269
+ }
270
+ /**
271
+ * How far before the run's spawn moment a `turn_context` timestamp may sit and
272
+ * still count as THIS run's turn (sub-second ordering between the host's
273
+ * spawn clock and the CLI's own stamps).
274
+ */
275
+ const ROLLOUT_CONTEXT_SLACK_MS = 2_000;
276
+ /**
277
+ * Locate ONE run's rollout file. Walks the scoped home's `sessions/YYYY/MM/DD/`
278
+ * tree reading each file's head (the same bounded walk as
279
+ * {@link listCodexSessions}), then prefers the file whose session id equals
280
+ * `locator.threadId`; otherwise the file whose start falls in the window
281
+ * around `locator.windowStart`, newest first.
282
+ *
283
+ * **Concurrency**: the window alone cannot separate two runs started seconds
284
+ * apart, so when the locator names a `cwd` the window set is restricted to
285
+ * files whose head records that same directory. A window that holds candidates
286
+ * but none in the run's directory resolves to NOTHING rather than to a
287
+ * neighbour's file — unless the window holds exactly one candidate, where
288
+ * there is no other run to confuse it with and the mismatch is path skew (a
289
+ * symlinked temp root) rather than ambiguity. A misattributed model is a
290
+ * worse answer than an absent one: the evaluation fails a run loud on
291
+ * declared ≠ observed.
292
+ * @param homeDir - the `codex` harness's scoped home.
293
+ * @param locator - the thread id, working directory, and/or start-time window.
294
+ * @returns the located candidate, or undefined when nothing locates.
295
+ */
296
+ async function locateCodexRollout(homeDir, locator) {
297
+ const candidates = [];
298
+ let years;
299
+ try {
300
+ years = await readdir(join(homeDir, SESSIONS_ROOT));
301
+ }
302
+ catch (error) {
303
+ if (error.code === 'ENOENT')
304
+ return undefined;
305
+ throw error;
306
+ }
307
+ for (const year of years) {
308
+ if (!/^\d{4}$/.test(year))
309
+ continue;
310
+ let months;
311
+ try {
312
+ months = await readdir(join(homeDir, SESSIONS_ROOT, year));
313
+ }
314
+ catch {
315
+ continue;
316
+ }
317
+ for (const month of months) {
318
+ if (!/^\d{2}$/.test(month))
319
+ continue;
320
+ let days;
321
+ try {
322
+ days = await readdir(join(homeDir, SESSIONS_ROOT, year, month));
323
+ }
324
+ catch {
325
+ continue;
326
+ }
327
+ for (const day of days) {
328
+ if (!/^\d{2}$/.test(day))
329
+ continue;
330
+ let files;
331
+ try {
332
+ files = await readdir(join(homeDir, SESSIONS_ROOT, year, month, day));
333
+ }
334
+ catch {
335
+ continue;
336
+ }
337
+ for (const name of files) {
338
+ if (!name.startsWith('rollout-') || !name.endsWith('.jsonl'))
339
+ continue;
340
+ const path = join(homeDir, SESSIONS_ROOT, year, month, day, name);
341
+ try {
342
+ const handle = await open(path, 'r');
343
+ let text;
344
+ try {
345
+ const buffer = Buffer.alloc(HEAD_BYTES);
346
+ const { bytesRead } = await handle.read(buffer, 0, HEAD_BYTES, 0);
347
+ text = buffer.subarray(0, bytesRead).toString('utf8');
348
+ }
349
+ finally {
350
+ await handle.close();
351
+ }
352
+ const record = rolloutRecord(text);
353
+ candidates.push({
354
+ path,
355
+ id: record?.id,
356
+ startedAt: record?.startedAt ?? rolloutFilenameTimestamp(name),
357
+ workDir: record?.workDir,
358
+ cliVersion: rolloutCliVersion(text),
359
+ });
360
+ }
361
+ catch {
362
+ continue;
363
+ }
364
+ }
365
+ }
366
+ }
367
+ }
368
+ if (candidates.length === 0)
369
+ return undefined;
370
+ const threadMatch = locator.threadId === undefined
371
+ ? undefined
372
+ : candidates.find(candidate => candidate.id === locator.threadId);
373
+ if (threadMatch !== undefined)
374
+ return threadMatch;
375
+ if (locator.windowStart === undefined)
376
+ return undefined;
377
+ const windowStart = locator.windowStart - ROLLOUT_WINDOW_SLACK_MS;
378
+ const windowEnd = Date.now() + ROLLOUT_WINDOW_FUTURE_MS;
379
+ const inWindow = candidates
380
+ .filter(candidate => candidate.startedAt !== undefined
381
+ && candidate.startedAt >= windowStart && candidate.startedAt <= windowEnd)
382
+ .sort((left, right) => (right.startedAt ?? 0) - (left.startedAt ?? 0));
383
+ if (locator.cwd === undefined)
384
+ return inWindow[0];
385
+ const wanted = resolve(locator.cwd);
386
+ const sameCwd = inWindow.filter(candidate => candidate.workDir !== undefined && resolve(candidate.workDir) === wanted);
387
+ if (sameCwd.length > 0)
388
+ return sameCwd[0];
389
+ return inWindow.length === 1 ? inWindow[0] : undefined;
390
+ }
391
+ /**
392
+ * Scan rollout text for this run's two read-back facts: the LAST `token_count`
393
+ * usage, and the model of the LAST `turn_context` whose top-level `timestamp`
394
+ * falls inside the run's window (`windowStart` onward) — the turn codex
395
+ * actually started for this round, so a resumed thread's earlier rounds are
396
+ * never misread and a round killed mid-turn still observes the model it
397
+ * started with. A torn line is skipped.
398
+ */
399
+ function scanRolloutText(text, windowStart) {
400
+ const usage = codexRolloutTokenUsage(text);
401
+ const scan = usage === undefined ? {} : { usage };
402
+ for (const raw of text.split('\n')) {
403
+ const line = raw.trim();
404
+ if (line === '')
405
+ continue;
406
+ let event;
407
+ try {
408
+ event = JSON.parse(line);
409
+ }
410
+ catch {
411
+ continue;
412
+ }
413
+ if (event.type !== 'turn_context' || typeof event.payload !== 'object' || event.payload === null)
414
+ continue;
415
+ if (windowStart !== undefined) {
416
+ const stamped = typeof event.timestamp === 'string' ? Date.parse(event.timestamp) : undefined;
417
+ if (stamped === undefined || !Number.isFinite(stamped) || stamped < windowStart)
418
+ continue;
419
+ }
420
+ const candidate = event.payload.model;
421
+ if (typeof candidate === 'string' && candidate !== '')
422
+ scan.model = candidate;
423
+ }
424
+ return scan;
425
+ }
426
+ /**
427
+ * Read one located rollout file for the round's usage and model.
428
+ *
429
+ * The tail is read first because it is where `token_count` lives (codex writes
430
+ * one per turn boundary, so the last is at the end). `turn_context` is the
431
+ * opposite: codex writes it when the turn STARTS, so a round that then
432
+ * produces more than {@link TAIL_BYTES} of events pushes it out of the tail
433
+ * entirely — which is exactly how a real evaluation run reported a null model
434
+ * against rollout files that named it plainly, while a short smoke round (a
435
+ * whole file smaller than the tail window) read back fine. So whenever the
436
+ * tail leaves either fact missing, a bounded full read from offset 0 fills it
437
+ * in.
438
+ */
439
+ async function readRolloutFacts(path, windowStart) {
440
+ const handle = await open(path, 'r');
441
+ try {
442
+ const { size } = await handle.stat();
443
+ if (size === 0)
444
+ return {};
445
+ const tailLength = Math.min(TAIL_BYTES, size);
446
+ const tail = Buffer.alloc(tailLength);
447
+ const tailRead = await handle.read(tail, 0, tailLength, size - tailLength);
448
+ const scan = scanRolloutText(tail.subarray(0, tailRead.bytesRead).toString('utf8'), windowStart);
449
+ if ((scan.usage !== undefined && scan.model !== undefined) || size <= TAIL_BYTES)
450
+ return scan;
451
+ const full = Buffer.alloc(Math.min(FULL_BYTES, size));
452
+ const fullRead = await handle.read(full, 0, full.length, 0);
453
+ const wide = scanRolloutText(full.subarray(0, fullRead.bytesRead).toString('utf8'), windowStart);
454
+ // The tail's answers are the file's LAST ones; the full read only fills
455
+ // what the tail did not carry (its own "last" stops at FULL_BYTES).
456
+ const usage = scan.usage ?? wide.usage;
457
+ const model = scan.model ?? wide.model;
458
+ return {
459
+ ...usage === undefined ? {} : { usage },
460
+ ...model === undefined ? {} : { model },
461
+ };
462
+ }
463
+ finally {
464
+ await handle.close();
465
+ }
466
+ }
467
+ /**
468
+ * Read ONE run's facts back from its rollout file: the token usage a
469
+ * non-completed round never streamed, the model the exec `--json` wire does
470
+ * not carry at all (codex 0.144.0), and the codex build that served it. One
471
+ * locate and one file open serve all three.
472
+ *
473
+ * Every field is absent on a miss — no rollout file, an unreadable home, a
474
+ * hard kill before any turn boundary, a window with no turn_context. Absence
475
+ * is recorded, never guessed.
476
+ * @param homeDir - the `codex` harness's scoped home.
477
+ * @param locator - the thread id, working directory, and/or start-time window.
478
+ * @returns the round's read-back facts.
479
+ */
480
+ export async function codexRolloutRoundFacts(homeDir, locator) {
481
+ let located;
482
+ try {
483
+ located = await locateCodexRollout(homeDir, locator);
484
+ }
485
+ catch {
486
+ return {};
487
+ }
488
+ if (located === undefined)
489
+ return {};
490
+ const windowStart = locator.windowStart === undefined
491
+ ? undefined
492
+ : locator.windowStart - ROLLOUT_CONTEXT_SLACK_MS;
493
+ let scan = {};
494
+ try {
495
+ scan = await readRolloutFacts(located.path, windowStart);
496
+ }
497
+ catch {
498
+ // An unreadable file still yields the head fact the locator already read.
499
+ }
500
+ return {
501
+ ...scan.usage === undefined ? {} : { usage: scan.usage },
502
+ ...scan.model === undefined ? {} : { model: scan.model },
503
+ ...located.cliVersion === undefined ? {} : { cliVersion: located.cliVersion },
504
+ };
505
+ }
package/locale/en.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Codex Delegation",
4
+ "description": "Delegate coding tasks to your locally installed Codex CLI."
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Codex 委派",
4
+ "description": "把编码任务委派给本地 Codex CLI,在隔离作用域中运行。"
5
+ }
6
+ }
package/package.json ADDED
@@ -0,0 +1,106 @@
1
+ {
2
+ "name": "@khorsheed/dsh-local-agent-codex",
3
+ "description": "Codex harness for dsh-local-agent: scoped CODEX_HOME, one-shot codex exec delegation visible in the 子代理 surface, rollout-file session records",
4
+ "keywords": [
5
+ "dsh",
6
+ "dsh-plugin",
7
+ "cordis",
8
+ "local-agent",
9
+ "codex"
10
+ ],
11
+ "version": "0.1.0-rc.6",
12
+ "type": "module",
13
+ "main": "lib/index.js",
14
+ "types": "lib/types/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./lib/types/index.d.ts",
18
+ "default": "./lib/index.js"
19
+ },
20
+ "./invariant": {
21
+ "types": "./lib/types/invariant.d.ts",
22
+ "default": "./lib/invariant.js"
23
+ },
24
+ "./records": {
25
+ "types": "./lib/types/records.d.ts",
26
+ "default": "./lib/records.js"
27
+ },
28
+ "./client": {
29
+ "types": "./lib/types/client/index.d.ts",
30
+ "default": "./lib/client.js"
31
+ },
32
+ "./src/*": "./src/*",
33
+ "./cordis.patch.yml": "./cordis.patch.yml",
34
+ "./package.json": "./package.json",
35
+ "./locale/*.json": "./locale/*.json"
36
+ },
37
+ "dsh": {
38
+ "references": [
39
+ "@khorsheed/dsh-bundle-local-agent"
40
+ ],
41
+ "client": {
42
+ "inject": [
43
+ "@deepseek-ai/dsh-api-remotes",
44
+ "@deepseek-ai/dsh-client-locale",
45
+ "@deepseek-ai/dsh-client-ui-primitives",
46
+ "@deepseek-ai/dsh-client-ui-settings",
47
+ "@deepseek-ai/dsh-client-ui-slots"
48
+ ],
49
+ "platform": "web"
50
+ },
51
+ "bundle": {
52
+ "patch": "./cordis.patch.yml"
53
+ },
54
+ "compat": {
55
+ "minHost": "0.1.5-rc.1",
56
+ "notes": "Live output uses the local-agent transient Remote and public Conversation nodes, with incremental durable recovery checkpoints; native final messages retain their existing transcript and usage semantics. End-to-end latency acceptance is tracked by the room coordinator proposal.",
57
+ "verifiedHost": "0.1.5-rc.1"
58
+ }
59
+ },
60
+ "files": [
61
+ "lib",
62
+ "cordis.patch.yml",
63
+ "locale/*.json"
64
+ ],
65
+ "license": "MIT",
66
+ "dependencies": {
67
+ "@khorsheed/dsh-local-agent": "^0.1.0-rc.6",
68
+ "@khorsheed/dsh-local-agent-tool-subagent": "^0.1.0-rc.6"
69
+ },
70
+ "peerDependencies": {
71
+ "@deepseek-ai/cordis": "^4.0.1",
72
+ "@deepseek-ai/dsh-api-remotes": "^0.1.0-rc.6",
73
+ "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
74
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.6",
75
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6",
76
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
77
+ "@deepseek-ai/dsh-commands": "^0.1.0-rc.8",
78
+ "@deepseek-ai/dsh-invariants": "^0.1.0-rc.6",
79
+ "@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
80
+ "@deepseek-ai/dsh-session": "^0.1.0-rc.8",
81
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
82
+ "@deepseek-ai/dsh-subagent": "^0.1.0-rc.6",
83
+ "@deepseek-ai/dsh-subprocess": "^0.1.0-rc.6",
84
+ "react": "^18.2.0"
85
+ },
86
+ "peerDependenciesMeta": {
87
+ "@deepseek-ai/dsh-api-remotes": {
88
+ "optional": true
89
+ },
90
+ "@deepseek-ai/dsh-client-locale": {
91
+ "optional": true
92
+ },
93
+ "@deepseek-ai/dsh-client-ui-primitives": {
94
+ "optional": true
95
+ },
96
+ "@deepseek-ai/dsh-client-ui-settings": {
97
+ "optional": true
98
+ },
99
+ "@deepseek-ai/dsh-client-ui-slots": {
100
+ "optional": true
101
+ },
102
+ "react": {
103
+ "optional": true
104
+ }
105
+ }
106
+ }