thinkpool-pair 0.7.364 → 0.7.366
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/LICENSE +18 -0
- package/README.md +20 -0
- package/abort-turn-barrier.mjs +1 -23
- package/account.mjs +1 -1437
- package/acp-client.mjs +1 -140
- package/agent-detect.mjs +1 -28
- package/agent-notify.mjs +1 -142
- package/agent-visibility.mjs +1 -67
- package/auth-store.mjs +1 -120
- package/bridge.mjs +1 -6266
- package/byok-detect.mjs +1 -126
- package/claude-command-catalog.mjs +1 -91
- package/claude-session.mjs +1 -1519
- package/code-event-contract.mjs +1 -118
- package/codex-app-server.mjs +1 -340
- package/codex-commands.mjs +1 -83
- package/codex-event-mapper.mjs +1 -232
- package/codex-images.mjs +1 -69
- package/codex-mcp-http.mjs +1 -131
- package/codex-session.mjs +1 -1295
- package/command-catalog.mjs +1 -118
- package/command-guidance.mjs +1 -8
- package/context-contract.mjs +1 -95
- package/context-windows.mjs +1 -107
- package/cross-terminal.mjs +1 -789
- package/cumulative-event-relay.mjs +1 -53
- package/design-edit.mjs +1 -424
- package/design-source-contract.mjs +1 -4
- package/direct-pair-room.mjs +1 -57
- package/dispatch-lease.mjs +1 -37
- package/dispatch-permission-cleanup.mjs +1 -86
- package/edit-diff.mjs +1 -136
- package/error-recovery.mjs +1 -50
- package/event-bounds.mjs +1 -121
- package/event-delivery-queue.mjs +1 -60
- package/event-id.mjs +1 -549
- package/evidence-citations.mjs +1 -50
- package/evidence-compact.mjs +1 -11
- package/flow-assembly.mjs +1 -196
- package/flow-budget.mjs +1 -84
- package/flow-conductor.mjs +1 -259
- package/flow-context-store.mjs +1 -387
- package/flow-host-revert.mjs +1 -42
- package/flow-models.mjs +1 -139
- package/flow-preview.mjs +1 -148
- package/flow-receipt.mjs +1 -122
- package/flow-redispatch.mjs +1 -72
- package/flow-review-gate.mjs +1 -402
- package/flow-review-reflect.mjs +1 -115
- package/flow-review.mjs +1 -152
- package/flow-scope-evidence.mjs +1 -117
- package/flow-skill-registry.mjs +1 -140
- package/flow-task-graph.mjs +1 -562
- package/flow-worktree.mjs +1 -71
- package/git-diff-report.mjs +1 -121
- package/hermes-delegation-guard.mjs +1 -14
- package/hermes-event-mapper.mjs +1 -194
- package/hermes-isolation.mjs +1 -61
- package/hermes-model-cache.mjs +1 -54
- package/hermes-policy.mjs +1 -92
- package/hermes-probe.mjs +1 -57
- package/hermes-session.mjs +1 -683
- package/hermes-setup.mjs +1 -167
- package/host-memory.mjs +1 -116
- package/interrupted-resume.mjs +1 -95
- package/keep-awake.mjs +1 -148
- package/key-shape.mjs +1 -49
- package/lane-continuation.mjs +1 -83
- package/lane-lifecycle.mjs +1 -189
- package/lane-worktree.mjs +1 -77
- package/launcher.mjs +1 -354
- package/mcp-flight-recorder.mjs +1 -79
- package/mockup-delivery.mjs +1 -57
- package/model-prices.mjs +1 -113
- package/package.json +13 -4
- package/pair-bus.mjs +1 -98
- package/pair-control-authority.mjs +1 -89
- package/past-work-search.mjs +1 -105
- package/plan-meters.mjs +1 -144
- package/presence.mjs +1 -191
- package/privacy-report.mjs +1 -108
- package/provider-resilience.mjs +1 -356
- package/provider.mjs +1 -133
- package/providers.mjs +1 -491
- package/publish-guard.mjs +2 -0
- package/publish-manifest.json +129 -0
- package/question-response.mjs +1 -58
- package/reap-terminal.mjs +1 -68
- package/recap.mjs +1 -297
- package/replay-transport.mjs +1 -64
- package/repo-search.mjs +1 -190
- package/review-check.mjs +1 -182
- package/runtime-contract.mjs +1 -93
- package/runtime-registry.mjs +1 -64
- package/runtime-session.mjs +1 -20
- package/scheduled-run-admission.mjs +1 -364
- package/scheduled-runs.mjs +1 -268
- package/sdk-admission.mjs +1 -9
- package/sdk-smoke.mjs +1 -61
- package/serve-consent.mjs +1 -118
- package/serve-dir.mjs +1 -40
- package/service.mjs +1 -877
- package/session-store.mjs +1 -484
- package/side-lane.mjs +1 -63
- package/supabase-key.mjs +1 -176
- package/supervisor-ready.mjs +1 -57
- package/switch-provider.mjs +1 -84
- package/terminal-name.mjs +1 -359
- package/terminal-row-reconcile.mjs +1 -70
- package/thinkpool-prompt-contracts.mjs +1 -85
- package/thinkpool-room-prompt.mjs +1 -187
- package/transcript-sanitize.mjs +1 -332
- package/turn-stall.mjs +1 -61
- package/update-gate.mjs +1 -53
- package/viewport.mjs +1 -810
- package/worker-completion.mjs +1 -57
package/code-event-contract.mjs
CHANGED
|
@@ -1,118 +1 @@
|
|
|
1
|
-
|
|
2
|
-
Keep this module browser-safe: the bridge and src/pages/code both consume it. */
|
|
3
|
-
|
|
4
|
-
import { recoveryCodeEvent } from './error-recovery.mjs'
|
|
5
|
-
|
|
6
|
-
export const EVENT_CLASS = Object.freeze({
|
|
7
|
-
VOLATILE_PRESENCE: 'volatile-presence',
|
|
8
|
-
REPLAYABLE_PROGRESS: 'replayable-progress',
|
|
9
|
-
REPLAYABLE_COMMAND: 'replayable-command',
|
|
10
|
-
DURABLE_DELIVERY_EVIDENCE: 'durable-delivery-evidence',
|
|
11
|
-
UNKNOWN: 'unknown',
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
const V = EVENT_CLASS.VOLATILE_PRESENCE
|
|
15
|
-
const P = EVENT_CLASS.REPLAYABLE_PROGRESS
|
|
16
|
-
const C = EVENT_CLASS.REPLAYABLE_COMMAND
|
|
17
|
-
const E = EVENT_CLASS.DURABLE_DELIVERY_EVIDENCE
|
|
18
|
-
|
|
19
|
-
// Exhaustive over every kind currently emitted into a Code room, its retained log,
|
|
20
|
-
// or the durable __agent notification stream. `answer`/`control`/etc. are commands
|
|
21
|
-
// replayed by the transcript; they are not durable decision records. Pair control
|
|
22
|
-
// supplies actor-bound decision records and authoritative verification receipts.
|
|
23
|
-
// Adding a producer without adding its kind here is a test/dev error and a
|
|
24
|
-
// fail-closed production value.
|
|
25
|
-
export const CODE_EVENT_REGISTRY = Object.freeze({
|
|
26
|
-
system: V,
|
|
27
|
-
thinking_tokens: V,
|
|
28
|
-
effort: V,
|
|
29
|
-
mode: V,
|
|
30
|
-
usage: V,
|
|
31
|
-
suggestion: V,
|
|
32
|
-
stalled: V,
|
|
33
|
-
compact: V,
|
|
34
|
-
assistant_stream: V,
|
|
35
|
-
models: V,
|
|
36
|
-
'model-switch': V,
|
|
37
|
-
capabilities: V,
|
|
38
|
-
'update-available': V,
|
|
39
|
-
|
|
40
|
-
you: P,
|
|
41
|
-
assistant: P,
|
|
42
|
-
tool_result: P,
|
|
43
|
-
note: P,
|
|
44
|
-
pool: P,
|
|
45
|
-
chat: P,
|
|
46
|
-
whisper: P,
|
|
47
|
-
compaction: P,
|
|
48
|
-
'side-started': P,
|
|
49
|
-
'side-handback': P,
|
|
50
|
-
answer: C,
|
|
51
|
-
control: C,
|
|
52
|
-
'repo-search': C,
|
|
53
|
-
clear: C,
|
|
54
|
-
'queue-add': C,
|
|
55
|
-
'queue-cancel': C,
|
|
56
|
-
propose: C,
|
|
57
|
-
'propose-done': C,
|
|
58
|
-
|
|
59
|
-
result: E,
|
|
60
|
-
error: E,
|
|
61
|
-
mockup: E,
|
|
62
|
-
image: E,
|
|
63
|
-
'needs-input': E,
|
|
64
|
-
'needs-resolved': E,
|
|
65
|
-
continuation: E,
|
|
66
|
-
'turn-done': E,
|
|
67
|
-
'flow-review-receipt': E,
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
export const CODE_EVENT_KINDS = Object.freeze(Object.keys(CODE_EVENT_REGISTRY))
|
|
71
|
-
|
|
72
|
-
const strictByDefault = () =>
|
|
73
|
-
typeof process !== 'undefined' && (process.env?.NODE_ENV === 'test' || process.env?.NODE_ENV === 'development')
|
|
74
|
-
|
|
75
|
-
export function classifyCodeEvent (eventOrKind, { strict = strictByDefault() } = {}) {
|
|
76
|
-
const kind = typeof eventOrKind === 'string' ? eventOrKind : eventOrKind?.kind
|
|
77
|
-
const eventClass = typeof kind === 'string' ? CODE_EVENT_REGISTRY[kind] : null
|
|
78
|
-
if (!eventClass) {
|
|
79
|
-
if (strict) throw new TypeError(`Unknown ThinkPool Code event kind: ${String(kind)}`)
|
|
80
|
-
return Object.freeze({ kind: kind ?? null, known: false, eventClass: EVENT_CLASS.UNKNOWN, durable: false, replayable: false, authorizes: false, completes: false })
|
|
81
|
-
}
|
|
82
|
-
return Object.freeze({
|
|
83
|
-
kind,
|
|
84
|
-
known: true,
|
|
85
|
-
eventClass,
|
|
86
|
-
// A replayable command is historical UI context, not a durable decision fact.
|
|
87
|
-
// Only delivery evidence currently has the archive-level durability contract.
|
|
88
|
-
durable: eventClass === E,
|
|
89
|
-
replayable: eventClass !== V,
|
|
90
|
-
// Pair control supplies actor-bound authorization. Classification alone never does.
|
|
91
|
-
authorizes: false,
|
|
92
|
-
// `completes` means an authority-verified outcome, not a runtime turn boundary.
|
|
93
|
-
// A successful result can settle execution while remaining unverified.
|
|
94
|
-
completes: false,
|
|
95
|
-
})
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Preserve observability without copying an unregistered payload into the replay
|
|
99
|
-
// or archive. This is a bridge-observed delivery failure, not a claim about what
|
|
100
|
-
// the producer meant or a substitute for its event.
|
|
101
|
-
export function unknownCodeEventNotice (eventOrKind) {
|
|
102
|
-
const rawKind = typeof eventOrKind === 'string' ? eventOrKind : eventOrKind?.kind
|
|
103
|
-
const unknownKind = typeof rawKind === 'string' ? rawKind.slice(0, 120) : null
|
|
104
|
-
return {
|
|
105
|
-
kind: 'error',
|
|
106
|
-
subtype: 'unknown_event',
|
|
107
|
-
message: `Unrecognized Code event${unknownKind ? ` "${unknownKind}"` : ''} was rejected; this lane needs recovery.`,
|
|
108
|
-
unknownKind,
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export const isKnownCodeEventKind = (kind) => Object.hasOwn(CODE_EVENT_REGISTRY, kind)
|
|
113
|
-
|
|
114
|
-
// Normalize recovery reporting before it enters the durable room log. This is a
|
|
115
|
-
// projection only: adapters retain their existing retry and session behavior.
|
|
116
|
-
export function boundedRecoveryCodeEvent(input) {
|
|
117
|
-
return recoveryCodeEvent(input)
|
|
118
|
-
}
|
|
1
|
+
import{recoveryCodeEvent as e}from"./error-recovery.mjs";export const EVENT_CLASS=Object.freeze({VOLATILE_PRESENCE:"volatile-presence",REPLAYABLE_PROGRESS:"replayable-progress",REPLAYABLE_COMMAND:"replayable-command",DURABLE_DELIVERY_EVIDENCE:"durable-delivery-evidence",UNKNOWN:"unknown"});const n=EVENT_CLASS.VOLATILE_PRESENCE,t=EVENT_CLASS.REPLAYABLE_PROGRESS,o=EVENT_CLASS.REPLAYABLE_COMMAND,r=EVENT_CLASS.DURABLE_DELIVERY_EVIDENCE;export const CODE_EVENT_REGISTRY=Object.freeze({system:n,thinking_tokens:n,effort:n,mode:n,usage:n,suggestion:n,stalled:n,compact:n,assistant_stream:n,models:n,"model-switch":n,capabilities:n,"update-available":n,you:t,assistant:t,tool_result:t,note:t,pool:t,chat:t,whisper:t,compaction:t,"side-started":t,"side-handback":t,answer:o,control:o,"repo-search":o,clear:o,"queue-add":o,"queue-cancel":o,propose:o,"propose-done":o,result:r,error:r,mockup:r,image:r,"needs-input":r,"needs-resolved":r,continuation:r,"turn-done":r,"flow-review-receipt":r});export const CODE_EVENT_KINDS=Object.freeze(Object.keys(CODE_EVENT_REGISTRY));const s=()=>"undefined"!=typeof process&&("test"===process.env?.NODE_ENV||"development"===process.env?.NODE_ENV);export function classifyCodeEvent(e,{strict:t=s()}={}){const o="string"==typeof e?e:e?.kind,E="string"==typeof o?CODE_EVENT_REGISTRY[o]:null;if(!E){if(t)throw new TypeError(`Unknown ThinkPool Code event kind: ${String(o)}`);return Object.freeze({kind:o??null,known:!1,eventClass:EVENT_CLASS.UNKNOWN,durable:!1,replayable:!1,authorizes:!1,completes:!1})}return Object.freeze({kind:o,known:!0,eventClass:E,durable:E===r,replayable:E!==n,authorizes:!1,completes:!1})}export function unknownCodeEventNotice(e){const n="string"==typeof e?e:e?.kind,t="string"==typeof n?n.slice(0,120):null;return{kind:"error",subtype:"unknown_event",message:`Unrecognized Code event${t?` "${t}"`:""} was rejected; this lane needs recovery.`,unknownKind:t}}export const isKnownCodeEventKind=e=>Object.hasOwn(CODE_EVENT_REGISTRY,e);export function boundedRecoveryCodeEvent(n){return e(n)}
|
package/codex-app-server.mjs
CHANGED
|
@@ -1,340 +1 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
// App Server is still experimental, so the bridge enables it only for the exact
|
|
4
|
-
// Codex version range whose protocol we have exercised. TP_CODEX_APP_SERVER=0 is
|
|
5
|
-
// the kill switch; `codex exec --json` remains the safe fallback.
|
|
6
|
-
import { execFileSync, spawn } from 'node:child_process'
|
|
7
|
-
import readline from 'node:readline'
|
|
8
|
-
|
|
9
|
-
export const CODEX_APP_SERVER_ENV = 'TP_CODEX_APP_SERVER'
|
|
10
|
-
export const MIN_CODEX_APP_SERVER_VERSION = Object.freeze([0, 144, 1])
|
|
11
|
-
export const MAX_CODEX_APP_SERVER_VERSION = Object.freeze([0, 145, 0])
|
|
12
|
-
|
|
13
|
-
const parseVersion = (raw) => {
|
|
14
|
-
const m = String(raw || '').match(/(?:codex-cli\s+)?(\d+)\.(\d+)\.(\d+)/i)
|
|
15
|
-
return m ? m.slice(1, 4).map(Number) : null
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const atLeast = (actual, minimum) => {
|
|
19
|
-
for (let i = 0; i < 3; i++) {
|
|
20
|
-
if (actual[i] > minimum[i]) return true
|
|
21
|
-
if (actual[i] < minimum[i]) return false
|
|
22
|
-
}
|
|
23
|
-
return true
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const before = (actual, maximum) => !atLeast(actual, maximum)
|
|
27
|
-
|
|
28
|
-
const transportError = (message, delivery = 'unknown') => {
|
|
29
|
-
const error = message instanceof Error ? message : new Error(String(message || 'Codex App Server transport failed'))
|
|
30
|
-
error.delivery = delivery
|
|
31
|
-
return error
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function codexAppServerGate({ env = process.env, execFile = execFileSync } = {}) {
|
|
35
|
-
const setting = String(env?.[CODEX_APP_SERVER_ENV] || '').trim().toLowerCase()
|
|
36
|
-
if (['0', 'false', 'off'].includes(setting)) {
|
|
37
|
-
return { enabled: false, reason: `${CODEX_APP_SERVER_ENV} disables App Server`, version: null }
|
|
38
|
-
}
|
|
39
|
-
if (setting && !['1', 'true', 'on'].includes(setting)) {
|
|
40
|
-
return { enabled: false, reason: `${CODEX_APP_SERVER_ENV} has an invalid value`, version: null }
|
|
41
|
-
}
|
|
42
|
-
try {
|
|
43
|
-
const raw = execFile('codex', ['--version'], { encoding: 'utf8', env })
|
|
44
|
-
const parsed = parseVersion(raw)
|
|
45
|
-
if (!parsed) return { enabled: false, reason: `unrecognized Codex version: ${String(raw).trim()}`, version: null }
|
|
46
|
-
const version = parsed.join('.')
|
|
47
|
-
if (!atLeast(parsed, MIN_CODEX_APP_SERVER_VERSION)) {
|
|
48
|
-
return { enabled: false, reason: `Codex ${version} is older than ${MIN_CODEX_APP_SERVER_VERSION.join('.')}`, version }
|
|
49
|
-
}
|
|
50
|
-
if (!before(parsed, MAX_CODEX_APP_SERVER_VERSION)) {
|
|
51
|
-
return { enabled: false, reason: `Codex ${version} is newer than the tested App Server protocol (<${MAX_CODEX_APP_SERVER_VERSION.join('.')})`, version }
|
|
52
|
-
}
|
|
53
|
-
return { enabled: true, reason: null, version }
|
|
54
|
-
} catch (error) {
|
|
55
|
-
return { enabled: false, reason: `could not read Codex version: ${error?.message || error}`, version: null }
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function buildCodexAppServerArgs({ providerConfig, mcpUrl, writableDirs = [] } = {}) {
|
|
60
|
-
const args = ['app-server', '--stdio']
|
|
61
|
-
if (providerConfig) args.push('-c', providerConfig)
|
|
62
|
-
if (mcpUrl) {
|
|
63
|
-
args.push('-c', `mcp_servers.thinkpool.url=${JSON.stringify(mcpUrl)}`)
|
|
64
|
-
// The room prompt promises spawn/read/close_terminal as native room tools.
|
|
65
|
-
// Fail startup instead of silently running a tool-less Codex turn when the
|
|
66
|
-
// private loopback MCP handshake is unavailable. Existing MCP approval
|
|
67
|
-
// policy still owns the room permission card for each call.
|
|
68
|
-
args.push('-c', 'mcp_servers.thinkpool.required=true')
|
|
69
|
-
}
|
|
70
|
-
if (writableDirs.length) args.push('-c', `sandbox_workspace_write.writable_roots=${JSON.stringify(writableDirs)}`)
|
|
71
|
-
return args
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const rpcError = (message, code = -32000) => ({ code, message: String(message || 'App Server request failed') })
|
|
75
|
-
const turnInput = (text, images = []) => [
|
|
76
|
-
{ type: 'text', text: String(text ?? '') },
|
|
77
|
-
...images.filter(Boolean).map((imagePath) => ({ type: 'localImage', path: String(imagePath) })),
|
|
78
|
-
]
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Minimal App Server client. It deliberately accepts unknown notifications and
|
|
82
|
-
* fields: protocol additions must not take down an otherwise usable lane.
|
|
83
|
-
*/
|
|
84
|
-
export class CodexAppServerClient {
|
|
85
|
-
constructor({ cwd, env, args = ['app-server', '--stdio'], spawnImpl = spawn, onNotification, onServerRequest, onClose, initializeTimeoutMs = 3000, threadTimeoutMs = 5000, turnStartTimeoutMs = 5000, controlTimeoutMs = 5000 } = {}) {
|
|
86
|
-
this.cwd = cwd
|
|
87
|
-
this.env = env
|
|
88
|
-
this.args = args
|
|
89
|
-
this.spawnImpl = spawnImpl
|
|
90
|
-
this.onNotification = onNotification
|
|
91
|
-
this.onServerRequest = onServerRequest
|
|
92
|
-
this.onClose = onClose
|
|
93
|
-
this.initializeTimeoutMs = initializeTimeoutMs
|
|
94
|
-
this.threadTimeoutMs = threadTimeoutMs
|
|
95
|
-
this.turnStartTimeoutMs = turnStartTimeoutMs
|
|
96
|
-
this.controlTimeoutMs = controlTimeoutMs
|
|
97
|
-
this.child = null
|
|
98
|
-
this.nextId = 1
|
|
99
|
-
this.pending = new Map()
|
|
100
|
-
this.turnWaiters = new Map()
|
|
101
|
-
this.completedTurns = new Map()
|
|
102
|
-
this.mcpStartup = new Map()
|
|
103
|
-
this.mcpWaiters = new Map()
|
|
104
|
-
this.closedError = null
|
|
105
|
-
this.stderrTail = ''
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
get alive() { return !!this.child && !this.closedError }
|
|
109
|
-
|
|
110
|
-
async start() {
|
|
111
|
-
if (this.child) return
|
|
112
|
-
const child = this.spawnImpl('codex', this.args, { cwd: this.cwd, env: this.env, stdio: ['pipe', 'pipe', 'pipe'] })
|
|
113
|
-
this.child = child
|
|
114
|
-
const rl = readline.createInterface({ input: child.stdout, crlfDelay: Infinity })
|
|
115
|
-
rl.on('line', (line) => this._onLine(line))
|
|
116
|
-
child.stderr?.on?.('data', (chunk) => {
|
|
117
|
-
this.stderrTail += String(chunk)
|
|
118
|
-
if (this.stderrTail.length > 1200) this.stderrTail = this.stderrTail.slice(-1200)
|
|
119
|
-
})
|
|
120
|
-
child.stdin?.on?.('error', (error) => this._close(error))
|
|
121
|
-
child.on('error', (error) => this._close(error))
|
|
122
|
-
child.on('close', (code) => this._close(new Error(`codex app-server exited ${code ?? 'unknown'}${this.stderrTail ? `: ${this.stderrTail.trim().slice(-300)}` : ''}`)))
|
|
123
|
-
|
|
124
|
-
await this.request('initialize', {
|
|
125
|
-
clientInfo: { name: 'thinkpool-pair', title: 'ThinkPool Code', version: '1' },
|
|
126
|
-
capabilities: { experimentalApi: true },
|
|
127
|
-
}, this.initializeTimeoutMs)
|
|
128
|
-
this.notify('initialized')
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
_write(message) {
|
|
132
|
-
if (!this.child?.stdin || this.closedError) throw this.closedError || new Error('codex app-server is not running')
|
|
133
|
-
this.child.stdin.write(`${JSON.stringify(message)}\n`)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
request(method, params = {}, timeoutMs = 0) {
|
|
137
|
-
const id = this.nextId++
|
|
138
|
-
return new Promise((resolve, reject) => {
|
|
139
|
-
let timer = null
|
|
140
|
-
if (timeoutMs > 0) timer = setTimeout(() => {
|
|
141
|
-
this.pending.delete(id)
|
|
142
|
-
reject(transportError(`${method} timed out after ${timeoutMs}ms`, 'unknown'))
|
|
143
|
-
}, timeoutMs)
|
|
144
|
-
this.pending.set(id, { resolve, reject, timer, method })
|
|
145
|
-
try { this._write({ id, method, params }) }
|
|
146
|
-
catch (error) {
|
|
147
|
-
this.pending.delete(id)
|
|
148
|
-
if (timer) clearTimeout(timer)
|
|
149
|
-
reject(transportError(error, 'not_sent'))
|
|
150
|
-
}
|
|
151
|
-
})
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
notify(method, params) {
|
|
155
|
-
this._write(params === undefined ? { method } : { method, params })
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
respond(id, result) { this._write({ id, result }) }
|
|
159
|
-
respondError(id, error) { this._write({ id, error: rpcError(error?.message || error) }) }
|
|
160
|
-
|
|
161
|
-
_onLine(line) {
|
|
162
|
-
if (!String(line).trim()) return
|
|
163
|
-
let message
|
|
164
|
-
try { message = JSON.parse(line) } catch { return }
|
|
165
|
-
if (message.id != null && ('result' in message || 'error' in message) && !message.method) {
|
|
166
|
-
const pending = this.pending.get(message.id)
|
|
167
|
-
if (!pending) return
|
|
168
|
-
this.pending.delete(message.id)
|
|
169
|
-
if (pending.timer) clearTimeout(pending.timer)
|
|
170
|
-
if (message.error) {
|
|
171
|
-
const error = transportError(message.error.message || `${pending.method} failed`, 'rejected')
|
|
172
|
-
error.code = message.error.code
|
|
173
|
-
error.data = message.error.data
|
|
174
|
-
pending.reject(error)
|
|
175
|
-
}
|
|
176
|
-
else pending.resolve(message.result)
|
|
177
|
-
return
|
|
178
|
-
}
|
|
179
|
-
if (message.id != null && message.method) {
|
|
180
|
-
Promise.resolve().then(() => {
|
|
181
|
-
if (!this.onServerRequest) throw new Error(`Unsupported Codex App Server request: ${message.method}`)
|
|
182
|
-
return this.onServerRequest(message.method, message.params || {}, message.id)
|
|
183
|
-
})
|
|
184
|
-
.then((result) => { try { this.respond(message.id, result ?? {}) } catch { /* peer closed */ } })
|
|
185
|
-
.catch((error) => { try { this.respondError(message.id, error) } catch { /* peer closed */ } })
|
|
186
|
-
return
|
|
187
|
-
}
|
|
188
|
-
if (!message.method) return
|
|
189
|
-
if (message.method === 'turn/completed') {
|
|
190
|
-
const turn = message.params?.turn
|
|
191
|
-
const turnId = turn?.id
|
|
192
|
-
if (turnId) {
|
|
193
|
-
const waiter = this.turnWaiters.get(turnId)
|
|
194
|
-
if (waiter) { this.turnWaiters.delete(turnId); waiter.resolve(message.params) }
|
|
195
|
-
else {
|
|
196
|
-
this.completedTurns.set(turnId, message.params)
|
|
197
|
-
while (this.completedTurns.size > 32) this.completedTurns.delete(this.completedTurns.keys().next().value)
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
if (message.method === 'mcpServer/startupStatus/updated') {
|
|
202
|
-
const params = message.params || {}
|
|
203
|
-
const threadId = String(params.threadId || '')
|
|
204
|
-
const name = String(params.name || '')
|
|
205
|
-
if (threadId && name) {
|
|
206
|
-
const key = `${threadId}\0${name}`
|
|
207
|
-
this.mcpStartup.set(key, params)
|
|
208
|
-
const waiter = this.mcpWaiters.get(key)
|
|
209
|
-
if (waiter && params.status === 'ready') {
|
|
210
|
-
this.mcpWaiters.delete(key)
|
|
211
|
-
if (waiter.timer) clearTimeout(waiter.timer)
|
|
212
|
-
waiter.resolve(params)
|
|
213
|
-
} else if (waiter && (params.status === 'failed' || params.error || params.failureReason)) {
|
|
214
|
-
this.mcpWaiters.delete(key)
|
|
215
|
-
if (waiter.timer) clearTimeout(waiter.timer)
|
|
216
|
-
waiter.reject(new Error(`Codex MCP server ${name} failed to start: ${params.error || params.failureReason || params.status}`))
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
try { this.onNotification?.(message.method, message.params || {}) } catch { /* consumer isolation */ }
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
_close(error) {
|
|
224
|
-
if (this.closedError) return
|
|
225
|
-
this.closedError = transportError(error instanceof Error ? error : String(error || 'codex app-server closed'), 'unknown')
|
|
226
|
-
this.child = null
|
|
227
|
-
for (const { reject, timer } of this.pending.values()) { if (timer) clearTimeout(timer); reject(this.closedError) }
|
|
228
|
-
this.pending.clear()
|
|
229
|
-
for (const { reject } of this.turnWaiters.values()) reject(this.closedError)
|
|
230
|
-
this.turnWaiters.clear()
|
|
231
|
-
this.completedTurns.clear()
|
|
232
|
-
for (const { reject, timer } of this.mcpWaiters.values()) {
|
|
233
|
-
if (timer) clearTimeout(timer)
|
|
234
|
-
reject(this.closedError)
|
|
235
|
-
}
|
|
236
|
-
this.mcpWaiters.clear()
|
|
237
|
-
this.mcpStartup.clear()
|
|
238
|
-
try { this.onClose?.(this.closedError) } catch { /* consumer isolation */ }
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
async startThread({ threadId, cwd, model, sandbox, approvalPolicy, config } = {}) {
|
|
242
|
-
const params = { cwd, model, sandbox, approvalPolicy, ...(config ? { config } : {}) }
|
|
243
|
-
const result = threadId
|
|
244
|
-
? await this.request('thread/resume', { ...params, threadId }, this.threadTimeoutMs)
|
|
245
|
-
: await this.request('thread/start', params, this.threadTimeoutMs)
|
|
246
|
-
const id = result?.thread?.id || result?.threadId || threadId
|
|
247
|
-
if (!id) throw new Error('Codex App Server did not return a thread id')
|
|
248
|
-
return id
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
waitForMcpServer({ threadId, name, timeoutMs = this.threadTimeoutMs } = {}) {
|
|
252
|
-
const safeThreadId = String(threadId || '')
|
|
253
|
-
const safeName = String(name || '')
|
|
254
|
-
if (!safeThreadId || !safeName) return Promise.reject(new Error('Codex MCP readiness needs a thread id and server name'))
|
|
255
|
-
const key = `${safeThreadId}\0${safeName}`
|
|
256
|
-
const current = this.mcpStartup.get(key)
|
|
257
|
-
if (current?.status === 'ready') return Promise.resolve(current)
|
|
258
|
-
if (current && (current.status === 'failed' || current.error || current.failureReason)) {
|
|
259
|
-
return Promise.reject(new Error(`Codex MCP server ${safeName} failed to start: ${current.error || current.failureReason || current.status}`))
|
|
260
|
-
}
|
|
261
|
-
if (this.mcpWaiters.has(key)) return this.mcpWaiters.get(key).promise
|
|
262
|
-
let timer = null
|
|
263
|
-
let resolveWaiter
|
|
264
|
-
let rejectWaiter
|
|
265
|
-
const promise = new Promise((resolve, reject) => {
|
|
266
|
-
resolveWaiter = resolve
|
|
267
|
-
rejectWaiter = reject
|
|
268
|
-
if (timeoutMs > 0) timer = setTimeout(() => {
|
|
269
|
-
this.mcpWaiters.delete(key)
|
|
270
|
-
reject(new Error(`Codex MCP server ${safeName} did not become ready within ${timeoutMs}ms`))
|
|
271
|
-
}, timeoutMs)
|
|
272
|
-
})
|
|
273
|
-
this.mcpWaiters.set(key, { promise, resolve: resolveWaiter, reject: rejectWaiter, timer })
|
|
274
|
-
return promise
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
async startTurn({ threadId, input, images, model, effort, approvalPolicy, collaborationMode } = {}) {
|
|
278
|
-
const result = await this.request('turn/start', {
|
|
279
|
-
threadId,
|
|
280
|
-
input: turnInput(input, images),
|
|
281
|
-
...(model ? { model } : {}),
|
|
282
|
-
...(effort ? { effort } : {}),
|
|
283
|
-
...(approvalPolicy ? { approvalPolicy } : {}),
|
|
284
|
-
...(collaborationMode ? { collaborationMode } : {}),
|
|
285
|
-
}, this.turnStartTimeoutMs)
|
|
286
|
-
const turnId = result?.turn?.id || result?.turnId
|
|
287
|
-
if (!turnId) throw new Error('Codex App Server did not return a turn id')
|
|
288
|
-
return turnId
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
waitForTurn(turnId) {
|
|
292
|
-
if (this.completedTurns.has(turnId)) {
|
|
293
|
-
const result = this.completedTurns.get(turnId)
|
|
294
|
-
this.completedTurns.delete(turnId)
|
|
295
|
-
return Promise.resolve(result)
|
|
296
|
-
}
|
|
297
|
-
if (this.turnWaiters.has(turnId)) return Promise.reject(new Error(`Already waiting for Codex turn ${turnId}`))
|
|
298
|
-
return new Promise((resolve, reject) => this.turnWaiters.set(turnId, { resolve, reject }))
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
async steer({ threadId, turnId, input, images } = {}) {
|
|
302
|
-
return this.request('turn/steer', {
|
|
303
|
-
threadId,
|
|
304
|
-
expectedTurnId: turnId,
|
|
305
|
-
input: turnInput(input, images),
|
|
306
|
-
}, this.controlTimeoutMs)
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
interrupt({ threadId, turnId } = {}) {
|
|
310
|
-
return this.request('turn/interrupt', { threadId, turnId }, this.controlTimeoutMs)
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
compact({ threadId } = {}) {
|
|
314
|
-
return this.request('thread/compact/start', { threadId }, this.controlTimeoutMs)
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
accountUsage() {
|
|
318
|
-
return this.request('account/usage/read', null, this.controlTimeoutMs)
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
accountRateLimits() {
|
|
322
|
-
return this.request('account/rateLimits/read', null, this.controlTimeoutMs)
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
startReview({ threadId, target } = {}) {
|
|
326
|
-
return this.request('review/start', {
|
|
327
|
-
threadId,
|
|
328
|
-
target: target || { type: 'uncommittedChanges' },
|
|
329
|
-
delivery: 'inline',
|
|
330
|
-
}, this.turnStartTimeoutMs)
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
end() {
|
|
334
|
-
const child = this.child
|
|
335
|
-
this._close(new Error('codex app-server ended'))
|
|
336
|
-
if (child) { try { child.kill('SIGTERM') } catch { /* noop */ } }
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
export function createCodexAppServer(options) { return new CodexAppServerClient(options) }
|
|
1
|
+
import{execFileSync as e,spawn as t}from"node:child_process";import r from"node:readline";export const CODEX_APP_SERVER_ENV="TP_CODEX_APP_SERVER";export const MIN_CODEX_APP_SERVER_VERSION=Object.freeze([0,144,1]);export const MAX_CODEX_APP_SERVER_VERSION=Object.freeze([0,145,0]);const i=(e,t)=>{for(let r=0;r<3;r++){if(e[r]>t[r])return!0;if(e[r]<t[r])return!1}return!0},s=(e,t="unknown")=>{const r=e instanceof Error?e:new Error(String(e||"Codex App Server transport failed"));return r.delivery=t,r};export function codexAppServerGate({env:t=process.env,execFile:r=e}={}){const s=String(t?.[CODEX_APP_SERVER_ENV]||"").trim().toLowerCase();if(["0","false","off"].includes(s))return{enabled:!1,reason:`${CODEX_APP_SERVER_ENV} disables App Server`,version:null};if(s&&!["1","true","on"].includes(s))return{enabled:!1,reason:`${CODEX_APP_SERVER_ENV} has an invalid value`,version:null};try{const e=r("codex",["--version"],{encoding:"utf8",env:t}),s=(e=>{const t=String(e||"").match(/(?:codex-cli\s+)?(\d+)\.(\d+)\.(\d+)/i);return t?t.slice(1,4).map(Number):null})(e);if(!s)return{enabled:!1,reason:`unrecognized Codex version: ${String(e).trim()}`,version:null};const o=s.join(".");return i(s,MIN_CODEX_APP_SERVER_VERSION)?i(s,MAX_CODEX_APP_SERVER_VERSION)?{enabled:!1,reason:`Codex ${o} is newer than the tested App Server protocol (<${MAX_CODEX_APP_SERVER_VERSION.join(".")})`,version:o}:{enabled:!0,reason:null,version:o}:{enabled:!1,reason:`Codex ${o} is older than ${MIN_CODEX_APP_SERVER_VERSION.join(".")}`,version:o}}catch(e){return{enabled:!1,reason:`could not read Codex version: ${e?.message||e}`,version:null}}}export function buildCodexAppServerArgs({providerConfig:e,mcpUrl:t,writableDirs:r=[]}={}){const i=["app-server","--stdio"];return e&&i.push("-c",e),t&&(i.push("-c",`mcp_servers.thinkpool.url=${JSON.stringify(t)}`),i.push("-c","mcp_servers.thinkpool.required=true")),r.length&&i.push("-c",`sandbox_workspace_write.writable_roots=${JSON.stringify(r)}`),i}const o=(e,t=-32e3)=>({code:t,message:String(e||"App Server request failed")}),n=(e,t=[])=>[{type:"text",text:String(e??"")},...t.filter(Boolean).map(e=>({type:"localImage",path:String(e)}))];export class CodexAppServerClient{constructor({cwd:e,env:r,args:i=["app-server","--stdio"],spawnImpl:s=t,onNotification:o,onServerRequest:n,onClose:a,initializeTimeoutMs:d=3e3,threadTimeoutMs:c=5e3,turnStartTimeoutMs:l=5e3,controlTimeoutMs:h=5e3}={}){this.cwd=e,this.env=r,this.args=i,this.spawnImpl=s,this.onNotification=o,this.onServerRequest=n,this.onClose=a,this.initializeTimeoutMs=d,this.threadTimeoutMs=c,this.turnStartTimeoutMs=l,this.controlTimeoutMs=h,this.child=null,this.nextId=1,this.pending=new Map,this.turnWaiters=new Map,this.completedTurns=new Map,this.mcpStartup=new Map,this.mcpWaiters=new Map,this.closedError=null,this.stderrTail=""}get alive(){return!!this.child&&!this.closedError}async start(){if(this.child)return;const e=this.spawnImpl("codex",this.args,{cwd:this.cwd,env:this.env,stdio:["pipe","pipe","pipe"]});this.child=e,r.createInterface({input:e.stdout,crlfDelay:1/0}).on("line",e=>this._onLine(e)),e.stderr?.on?.("data",e=>{this.stderrTail+=String(e),this.stderrTail.length>1200&&(this.stderrTail=this.stderrTail.slice(-1200))}),e.stdin?.on?.("error",e=>this._close(e)),e.on("error",e=>this._close(e)),e.on("close",e=>this._close(new Error(`codex app-server exited ${e??"unknown"}${this.stderrTail?`: ${this.stderrTail.trim().slice(-300)}`:""}`))),await this.request("initialize",{clientInfo:{name:"thinkpool-pair",title:"ThinkPool Code",version:"1"},capabilities:{experimentalApi:!0}},this.initializeTimeoutMs),this.notify("initialized")}_write(e){if(!this.child?.stdin||this.closedError)throw this.closedError||new Error("codex app-server is not running");this.child.stdin.write(`${JSON.stringify(e)}\n`)}request(e,t={},r=0){const i=this.nextId++;return new Promise((o,n)=>{let a=null;r>0&&(a=setTimeout(()=>{this.pending.delete(i),n(s(`${e} timed out after ${r}ms`,"unknown"))},r)),this.pending.set(i,{resolve:o,reject:n,timer:a,method:e});try{this._write({id:i,method:e,params:t})}catch(e){this.pending.delete(i),a&&clearTimeout(a),n(s(e,"not_sent"))}})}notify(e,t){this._write(void 0===t?{method:e}:{method:e,params:t})}respond(e,t){this._write({id:e,result:t})}respondError(e,t){this._write({id:e,error:o(t?.message||t)})}_onLine(e){if(!String(e).trim())return;let t;try{t=JSON.parse(e)}catch{return}if(null!=t.id&&("result"in t||"error"in t)&&!t.method){const e=this.pending.get(t.id);if(!e)return;if(this.pending.delete(t.id),e.timer&&clearTimeout(e.timer),t.error){const r=s(t.error.message||`${e.method} failed`,"rejected");r.code=t.error.code,r.data=t.error.data,e.reject(r)}else e.resolve(t.result);return}if(null!=t.id&&t.method)Promise.resolve().then(()=>{if(!this.onServerRequest)throw new Error(`Unsupported Codex App Server request: ${t.method}`);return this.onServerRequest(t.method,t.params||{},t.id)}).then(e=>{try{this.respond(t.id,e??{})}catch{}}).catch(e=>{try{this.respondError(t.id,e)}catch{}});else if(t.method){if("turn/completed"===t.method){const e=t.params?.turn,r=e?.id;if(r){const e=this.turnWaiters.get(r);if(e)this.turnWaiters.delete(r),e.resolve(t.params);else for(this.completedTurns.set(r,t.params);this.completedTurns.size>32;)this.completedTurns.delete(this.completedTurns.keys().next().value)}}if("mcpServer/startupStatus/updated"===t.method){const e=t.params||{},r=String(e.threadId||""),i=String(e.name||"");if(r&&i){const t=`${r}\0${i}`;this.mcpStartup.set(t,e);const s=this.mcpWaiters.get(t);s&&"ready"===e.status?(this.mcpWaiters.delete(t),s.timer&&clearTimeout(s.timer),s.resolve(e)):s&&("failed"===e.status||e.error||e.failureReason)&&(this.mcpWaiters.delete(t),s.timer&&clearTimeout(s.timer),s.reject(new Error(`Codex MCP server ${i} failed to start: ${e.error||e.failureReason||e.status}`)))}}try{this.onNotification?.(t.method,t.params||{})}catch{}}}_close(e){if(!this.closedError){this.closedError=s(e instanceof Error?e:String(e||"codex app-server closed"),"unknown"),this.child=null;for(const{reject:e,timer:t}of this.pending.values())t&&clearTimeout(t),e(this.closedError);this.pending.clear();for(const{reject:e}of this.turnWaiters.values())e(this.closedError);this.turnWaiters.clear(),this.completedTurns.clear();for(const{reject:e,timer:t}of this.mcpWaiters.values())t&&clearTimeout(t),e(this.closedError);this.mcpWaiters.clear(),this.mcpStartup.clear();try{this.onClose?.(this.closedError)}catch{}}}async startThread({threadId:e,cwd:t,model:r,sandbox:i,approvalPolicy:s,config:o}={}){const n={cwd:t,model:r,sandbox:i,approvalPolicy:s,...o?{config:o}:{}},a=e?await this.request("thread/resume",{...n,threadId:e},this.threadTimeoutMs):await this.request("thread/start",n,this.threadTimeoutMs),d=a?.thread?.id||a?.threadId||e;if(!d)throw new Error("Codex App Server did not return a thread id");return d}waitForMcpServer({threadId:e,name:t,timeoutMs:r=this.threadTimeoutMs}={}){const i=String(e||""),s=String(t||"");if(!i||!s)return Promise.reject(new Error("Codex MCP readiness needs a thread id and server name"));const o=`${i}\0${s}`,n=this.mcpStartup.get(o);if("ready"===n?.status)return Promise.resolve(n);if(n&&("failed"===n.status||n.error||n.failureReason))return Promise.reject(new Error(`Codex MCP server ${s} failed to start: ${n.error||n.failureReason||n.status}`));if(this.mcpWaiters.has(o))return this.mcpWaiters.get(o).promise;let a,d,c=null;const l=new Promise((e,t)=>{a=e,d=t,r>0&&(c=setTimeout(()=>{this.mcpWaiters.delete(o),t(new Error(`Codex MCP server ${s} did not become ready within ${r}ms`))},r))});return this.mcpWaiters.set(o,{promise:l,resolve:a,reject:d,timer:c}),l}async startTurn({threadId:e,input:t,images:r,model:i,effort:s,approvalPolicy:o,collaborationMode:a}={}){const d=await this.request("turn/start",{threadId:e,input:n(t,r),...i?{model:i}:{},...s?{effort:s}:{},...o?{approvalPolicy:o}:{},...a?{collaborationMode:a}:{}},this.turnStartTimeoutMs),c=d?.turn?.id||d?.turnId;if(!c)throw new Error("Codex App Server did not return a turn id");return c}waitForTurn(e){if(this.completedTurns.has(e)){const t=this.completedTurns.get(e);return this.completedTurns.delete(e),Promise.resolve(t)}return this.turnWaiters.has(e)?Promise.reject(new Error(`Already waiting for Codex turn ${e}`)):new Promise((t,r)=>this.turnWaiters.set(e,{resolve:t,reject:r}))}async steer({threadId:e,turnId:t,input:r,images:i}={}){return this.request("turn/steer",{threadId:e,expectedTurnId:t,input:n(r,i)},this.controlTimeoutMs)}interrupt({threadId:e,turnId:t}={}){return this.request("turn/interrupt",{threadId:e,turnId:t},this.controlTimeoutMs)}compact({threadId:e}={}){return this.request("thread/compact/start",{threadId:e},this.controlTimeoutMs)}accountUsage(){return this.request("account/usage/read",null,this.controlTimeoutMs)}accountRateLimits(){return this.request("account/rateLimits/read",null,this.controlTimeoutMs)}startReview({threadId:e,target:t}={}){return this.request("review/start",{threadId:e,target:t||{type:"uncommittedChanges"},delivery:"inline"},this.turnStartTimeoutMs)}end(){const e=this.child;if(this._close(new Error("codex app-server ended")),e)try{e.kill("SIGTERM")}catch{}}}export function createCodexAppServer(e){return new CodexAppServerClient(e)}
|
package/codex-commands.mjs
CHANGED
|
@@ -1,83 +1 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
// This is intentionally NOT a transcription of the interactive `codex` TUI
|
|
4
|
-
// palette. Codex CLI 0.144.1's App Server schema has no slash-command catalog
|
|
5
|
-
// endpoint. Keep only a command which the room can actually execute, and keep
|
|
6
|
-
// bridge-wide controls in bridge.mjs where their shared state semantics live.
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Commands surfaced from the Codex runtime's system catalog.
|
|
10
|
-
*
|
|
11
|
-
* /compact is bridge-executed today: bridge.mjs clears the native Codex context
|
|
12
|
-
* and carries a bounded room recap into the next fresh thread. App Server also
|
|
13
|
-
* exposes thread/compact/start in 0.144.1, but the bridge path is the supported
|
|
14
|
-
* cross-transport implementation, including exec fallback and the room's
|
|
15
|
-
* durable recap semantics.
|
|
16
|
-
*/
|
|
17
|
-
export const CODEX_COMMAND_CATALOG = Object.freeze([])
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Evidence-backed command matrix for the installed Codex CLI (0.144.1).
|
|
21
|
-
* `surface` means what ThinkPool intentionally advertises; it is not a claim
|
|
22
|
-
* that an interactive terminal palette command works through App Server.
|
|
23
|
-
*/
|
|
24
|
-
export const CODEX_COMMAND_MATRIX = Object.freeze([
|
|
25
|
-
Object.freeze({ name: '/compact', surface: true, runtime: 'native', api: 'thread/compact/start', reason: 'Native App Server compaction; bounded-recap reset remains the exec fallback.' }),
|
|
26
|
-
Object.freeze({ name: '/model', surface: true, runtime: 'bridge', api: 'model/list', reason: 'Shared picker owns the model argument.' }),
|
|
27
|
-
Object.freeze({ name: '/clear', surface: true, runtime: 'bridge', api: null, reason: 'Shared reset clears visible lane history and starts a fresh native thread.' }),
|
|
28
|
-
Object.freeze({ name: '/usage', surface: true, runtime: 'bridge+native', api: 'account/usage/read + account/rateLimits/read', reason: 'Reports lane usage plus redacted provider totals and limits.' }),
|
|
29
|
-
Object.freeze({ name: '/credits', surface: true, runtime: 'native', api: 'account/rateLimits/read', reason: 'Reports balance/availability only; never account identity.' }),
|
|
30
|
-
Object.freeze({ name: '/review', surface: true, runtime: 'native', api: 'review/start', reason: 'Ordinary lanes only; Flow and reviewer structural restrictions win.' }),
|
|
31
|
-
])
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Explicit exclusions: present in an interactive CLI or App Server protocol,
|
|
35
|
-
* but not safe/meaningful Code-room commands. Never add these by inference.
|
|
36
|
-
*/
|
|
37
|
-
export const CODEX_COMMAND_EXCLUSIONS = Object.freeze([
|
|
38
|
-
'profile-global configuration, skills, plugins, marketplaces, and experimental features',
|
|
39
|
-
'updates, diagnostics/feedback upload, and external-agent import',
|
|
40
|
-
'credentials, login/logout, identity, and account mutation',
|
|
41
|
-
'thread archive/delete/fork/name/goal/rollback and hidden-agent controls',
|
|
42
|
-
'MCP OAuth, filesystem, shell, and interactive command-exec protocol methods',
|
|
43
|
-
'detached review and review from Flow/reviewer lanes, because visible structural restrictions win',
|
|
44
|
-
])
|
|
45
|
-
|
|
46
|
-
export function isCodexCompactCommand(text) {
|
|
47
|
-
return /^\s*\/compact\s*$/i.test(String(text || ''))
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function codexReviewTarget(text) {
|
|
51
|
-
const body = String(text || '').replace(/^\s*\/review\b/i, '').trim()
|
|
52
|
-
if (!body) return { type: 'uncommittedChanges' }
|
|
53
|
-
const base = body.match(/^base\s*:\s*(\S+)$/i)
|
|
54
|
-
if (base) return { type: 'baseBranch', branch: base[1] }
|
|
55
|
-
const commit = body.match(/^commit\s*:\s*([a-f0-9]{7,64})$/i)
|
|
56
|
-
if (commit) return { type: 'commit', sha: commit[1] }
|
|
57
|
-
return { type: 'custom', instructions: body }
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const pct = (value) => Number.isFinite(Number(value)) ? `${Math.round(Number(value))}%` : null
|
|
61
|
-
const reset = (value) => Number.isFinite(Number(value)) ? new Date(Number(value) * 1000).toISOString().replace('T', ' ').slice(0, 16) + 'Z' : null
|
|
62
|
-
|
|
63
|
-
export function codexLimitReportLine(snapshot) {
|
|
64
|
-
const limits = snapshot?.rateLimits || null
|
|
65
|
-
if (!limits) return 'Codex provider limits unavailable'
|
|
66
|
-
const parts = []
|
|
67
|
-
if (limits.primary?.usedPercent != null) parts.push(`primary ${pct(limits.primary.usedPercent)}${reset(limits.primary.resetsAt) ? ` · resets ${reset(limits.primary.resetsAt)}` : ''}`)
|
|
68
|
-
if (limits.secondary?.usedPercent != null) parts.push(`secondary ${pct(limits.secondary.usedPercent)}${reset(limits.secondary.resetsAt) ? ` · resets ${reset(limits.secondary.resetsAt)}` : ''}`)
|
|
69
|
-
return parts.length ? `Codex limits · ${parts.join(' · ')}` : 'Codex provider limits unavailable'
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function codexCreditsReportLine(snapshot) {
|
|
73
|
-
const credits = snapshot?.rateLimits?.credits
|
|
74
|
-
if (!credits) return 'Codex credits unavailable for this account'
|
|
75
|
-
if (credits.unlimited) return 'Codex credits · unlimited'
|
|
76
|
-
if (credits.balance != null) return `Codex credits · balance ${String(credits.balance)}`
|
|
77
|
-
return `Codex credits · ${credits.hasCredits ? 'available' : 'none available'}`
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function codexAccountUsageLine(snapshot) {
|
|
81
|
-
const total = snapshot?.summary?.lifetimeTokens
|
|
82
|
-
return Number.isFinite(Number(total)) ? `Codex account usage · ${Number(total).toLocaleString('en-US')} lifetime tokens` : null
|
|
83
|
-
}
|
|
1
|
+
export const CODEX_COMMAND_CATALOG=Object.freeze([]);export const CODEX_COMMAND_MATRIX=Object.freeze([Object.freeze({name:"/compact",surface:!0,runtime:"native",api:"thread/compact/start",reason:"Native App Server compaction; bounded-recap reset remains the exec fallback."}),Object.freeze({name:"/model",surface:!0,runtime:"bridge",api:"model/list",reason:"Shared picker owns the model argument."}),Object.freeze({name:"/clear",surface:!0,runtime:"bridge",api:null,reason:"Shared reset clears visible lane history and starts a fresh native thread."}),Object.freeze({name:"/usage",surface:!0,runtime:"bridge+native",api:"account/usage/read + account/rateLimits/read",reason:"Reports lane usage plus redacted provider totals and limits."}),Object.freeze({name:"/credits",surface:!0,runtime:"native",api:"account/rateLimits/read",reason:"Reports balance/availability only; never account identity."}),Object.freeze({name:"/review",surface:!0,runtime:"native",api:"review/start",reason:"Ordinary lanes only; Flow and reviewer structural restrictions win."})]);export const CODEX_COMMAND_EXCLUSIONS=Object.freeze(["profile-global configuration, skills, plugins, marketplaces, and experimental features","updates, diagnostics/feedback upload, and external-agent import","credentials, login/logout, identity, and account mutation","thread archive/delete/fork/name/goal/rollback and hidden-agent controls","MCP OAuth, filesystem, shell, and interactive command-exec protocol methods","detached review and review from Flow/reviewer lanes, because visible structural restrictions win"]);export function isCodexCompactCommand(e){return/^\s*\/compact\s*$/i.test(String(e||""))}export function codexReviewTarget(e){const t=String(e||"").replace(/^\s*\/review\b/i,"").trim();if(!t)return{type:"uncommittedChanges"};const r=t.match(/^base\s*:\s*(\S+)$/i);if(r)return{type:"baseBranch",branch:r[1]};const a=t.match(/^commit\s*:\s*([a-f0-9]{7,64})$/i);return a?{type:"commit",sha:a[1]}:{type:"custom",instructions:t}}const e=e=>Number.isFinite(Number(e))?`${Math.round(Number(e))}%`:null,t=e=>Number.isFinite(Number(e))?new Date(1e3*Number(e)).toISOString().replace("T"," ").slice(0,16)+"Z":null;export function codexLimitReportLine(r){const a=r?.rateLimits||null;if(!a)return"Codex provider limits unavailable";const n=[];return null!=a.primary?.usedPercent&&n.push(`primary ${e(a.primary.usedPercent)}${t(a.primary.resetsAt)?` · resets ${t(a.primary.resetsAt)}`:""}`),null!=a.secondary?.usedPercent&&n.push(`secondary ${e(a.secondary.usedPercent)}${t(a.secondary.resetsAt)?` · resets ${t(a.secondary.resetsAt)}`:""}`),n.length?`Codex limits · ${n.join(" · ")}`:"Codex provider limits unavailable"}export function codexCreditsReportLine(e){const t=e?.rateLimits?.credits;return t?t.unlimited?"Codex credits · unlimited":null!=t.balance?`Codex credits · balance ${String(t.balance)}`:"Codex credits · "+(t.hasCredits?"available":"none available"):"Codex credits unavailable for this account"}export function codexAccountUsageLine(e){const t=e?.summary?.lifetimeTokens;return Number.isFinite(Number(t))?`Codex account usage · ${Number(t).toLocaleString("en-US")} lifetime tokens`:null}
|