@sleep2agi/agent-network 2.3.0-preview.16 → 2.3.0-preview.160
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/README.md +35 -324
- package/dist/bin/anet.cjs +101 -0
- package/dist/bin/cli.d.ts +1 -0
- package/dist/bin/cli.js +20 -1
- package/dist/bin/goal-wake-log-render.d.ts +31 -0
- package/dist/src/active-network-task.d.ts +28 -0
- package/dist/src/batch-workdir.d.ts +9 -0
- package/dist/src/bootstrap-password-db.d.ts +13 -0
- package/dist/src/channel-attachments.d.ts +29 -0
- package/dist/src/channel-inbox-wake.d.ts +21 -0
- package/dist/src/channel-meta.d.ts +10 -0
- package/dist/src/channel-peer-reply.d.ts +41 -0
- package/dist/src/channel-task-trace.d.ts +14 -0
- package/dist/src/claude-vendor-env.d.ts +28 -0
- package/dist/src/cli-args.d.ts +8 -0
- package/dist/src/cli-auth-session.d.ts +47 -0
- package/dist/src/cli-errors.d.ts +41 -0
- package/dist/src/cli-exit.d.ts +11 -0
- package/dist/src/cli-help.d.ts +10 -0
- package/dist/src/cli-version-display.d.ts +16 -0
- package/dist/src/client-task-trace.d.ts +9 -0
- package/dist/src/client.js +1 -1
- package/dist/src/codex-adopt.d.ts +46 -0
- package/dist/src/codex-auth-fingerprint.d.ts +211 -0
- package/dist/src/codex-bin-pin.d.ts +23 -0
- package/dist/src/codex-commhub-mcp.d.ts +22 -0
- package/dist/src/codex-copresence-bridge-log.d.ts +44 -0
- package/dist/src/codex-copresence-env.d.ts +5 -0
- package/dist/src/codex-copresence-login-gate.d.ts +16 -0
- package/dist/src/codex-copresence-model.d.ts +23 -0
- package/dist/src/codex-copresence-preflight.d.ts +78 -0
- package/dist/src/codex-copresence-profile.d.ts +125 -0
- package/dist/src/codex-copresence-recovery.d.ts +67 -0
- package/dist/src/codex-copresence-resume-timeout.d.ts +28 -0
- package/dist/src/codex-copresence-rollout-guard.d.ts +36 -0
- package/dist/src/codex-copresence-rpc.d.ts +15 -0
- package/dist/src/codex-copresence-thread.d.ts +13 -0
- package/dist/src/codex-external-appserver.d.ts +128 -0
- package/dist/src/codex-fork-recovery.d.ts +47 -0
- package/dist/src/codex-home-enforce.d.ts +88 -0
- package/dist/src/codex-lifecycle-account.d.ts +104 -0
- package/dist/src/codex-lifecycle-canary.d.ts +20 -0
- package/dist/src/codex-lifecycle-facts.d.ts +53 -0
- package/dist/src/codex-lifecycle-fork.d.ts +91 -0
- package/dist/src/codex-lifecycle-preflight.d.ts +77 -0
- package/dist/src/codex-lifecycle-receipt.d.ts +53 -0
- package/dist/src/codex-lifecycle-restart.d.ts +88 -0
- package/dist/src/codex-login-share-guard.d.ts +146 -0
- package/dist/src/codex-menu.d.ts +99 -0
- package/dist/src/codex-model-default.d.ts +6 -0
- package/dist/src/codex-node-login.d.ts +98 -0
- package/dist/src/codex-pending-thread-restart.d.ts +69 -0
- package/dist/src/codex-recovery-lease.d.ts +39 -0
- package/dist/src/codex-recovery-resource-gate.d.ts +15 -0
- package/dist/src/codex-resume.d.ts +89 -0
- package/dist/src/codex-rollout-history-guard.d.ts +84 -0
- package/dist/src/codex-tui-client-health.d.ts +30 -0
- package/dist/src/commhub-response.d.ts +1 -0
- package/dist/src/controlled-upload.d.ts +83 -0
- package/dist/src/copresence-deps.d.ts +22 -0
- package/dist/src/copresence-identity.d.ts +363 -0
- package/dist/src/copresence-startup-diagnosis.d.ts +17 -0
- package/dist/src/daemon-adopt.d.ts +14 -0
- package/dist/src/daemon-anet-bin.d.ts +32 -0
- package/dist/src/daemon-capability-display.d.ts +110 -0
- package/dist/src/daemon-capability-display.js +21 -0
- package/dist/src/daemon-inherited-env.d.ts +9 -0
- package/dist/src/daemon-runtime-path-preflight.d.ts +22 -0
- package/dist/src/daemon-runtime-staleness.d.ts +13 -0
- package/dist/src/dashboard-managed-process.d.ts +35 -0
- package/dist/src/display-width.d.ts +27 -0
- package/dist/src/doctor-node-count.d.ts +28 -0
- package/dist/src/environ-alias.d.ts +14 -0
- package/dist/src/grok-attach-client.d.ts +129 -0
- package/dist/src/grok-build-drift.d.ts +15 -0
- package/dist/src/grok-copresence-disclosure.d.ts +11 -0
- package/dist/src/grok-copresence-orchestration.d.ts +75 -0
- package/dist/src/grok-copresence-profile.d.ts +65 -0
- package/dist/src/grok-create-mode.d.ts +19 -0
- package/dist/src/host-telemetry.d.ts +14 -0
- package/dist/src/hub-health-timeout.d.ts +4 -0
- package/dist/src/hub-server-version.d.ts +64 -0
- package/dist/src/hub-stop.d.ts +74 -0
- package/dist/src/hub-time-display.d.ts +20 -0
- package/dist/src/hub-version-skew.d.ts +19 -0
- package/dist/src/im/correlation-store.d.ts +34 -0
- package/dist/src/im/feishu/adapter.d.ts +54 -0
- package/dist/src/im/feishu/bridge.d.ts +78 -14
- package/dist/src/im/feishu/hub-upload.d.ts +1 -1
- package/dist/src/im/feishu/outbound-route.d.ts +62 -0
- package/dist/src/im/feishu/worker-lifecycle.d.ts +13 -0
- package/dist/src/im/feishu/worker.js +180 -153
- package/dist/src/inbox-drain.d.ts +20 -0
- package/dist/src/local-process-state.d.ts +12 -0
- package/dist/src/locale-diagnostic.d.ts +12 -0
- package/dist/src/login-client-label.d.ts +6 -0
- package/dist/src/ls-columns.d.ts +15 -0
- package/dist/src/mock-llm.d.ts +12 -0
- package/dist/src/node-activity-log.d.ts +4 -0
- package/dist/src/node-clone.d.ts +134 -0
- package/dist/src/node-delete-hub.d.ts +42 -0
- package/dist/src/node-files.d.ts +62 -0
- package/dist/src/node-locate.d.ts +60 -0
- package/dist/src/node-ls-all.d.ts +80 -0
- package/dist/src/node-menu.d.ts +40 -0
- package/dist/src/node-not-found.d.ts +1 -0
- package/dist/src/node-remote.d.ts +180 -0
- package/dist/src/node-running-guard.d.ts +9 -0
- package/dist/src/node-server-identity.d.ts +35 -0
- package/dist/src/node-server-payload.d.ts +66 -0
- package/dist/src/node-server-rules-file.d.ts +14 -0
- package/dist/src/node-server-version.d.ts +39 -0
- package/dist/src/node-server.js +8 -1
- package/dist/src/node-skills.d.ts +57 -0
- package/dist/src/normalize-runtime.d.ts +29 -2
- package/dist/src/offline-age.d.ts +29 -0
- package/dist/src/one-line-cell.d.ts +20 -0
- package/dist/src/opencode-agent-node-pair.d.ts +28 -0
- package/dist/src/opencode-auth-login.d.ts +43 -0
- package/dist/src/opencode-free-tier.d.ts +27 -0
- package/dist/src/opencode-generation-create.d.ts +27 -0
- package/dist/src/opencode-generation-record.d.ts +24 -0
- package/dist/src/opencode-launch-env.d.ts +13 -0
- package/dist/src/opencode-owner-mode.d.ts +6 -0
- package/dist/src/opencode-package-binary.d.ts +44 -0
- package/dist/src/opencode-pin.d.ts +61 -0
- package/dist/src/opencode-preset.d.ts +73 -0
- package/dist/src/opencode-runtime-binding.d.ts +30 -0
- package/dist/src/opencode-safe-root.d.ts +36 -0
- package/dist/src/opencode-smoke-env.d.ts +1 -0
- package/dist/src/opencode-versions.d.ts +44 -0
- package/dist/src/outbound-attachments.d.ts +60 -0
- package/dist/src/outbound-tool-names.d.ts +1 -0
- package/dist/src/owned-roots.d.ts +58 -0
- package/dist/src/owner-env-file.d.ts +2 -0
- package/dist/src/package-mode-preflight.d.ts +41 -0
- package/dist/src/posix-codex-copresence.d.ts +33 -0
- package/dist/src/posix-modes.d.ts +36 -0
- package/dist/src/primary-network.d.ts +23 -0
- package/dist/src/private-state.d.ts +10 -0
- package/dist/src/process-telemetry.d.ts +12 -0
- package/dist/src/profile-serialize.d.ts +1 -0
- package/dist/src/project-key.d.ts +1 -0
- package/dist/src/reply-local-links.d.ts +47 -0
- package/dist/src/reply-originator.d.ts +20 -0
- package/dist/src/resume-runtime-infer.d.ts +18 -0
- package/dist/src/rules-file.d.ts +53 -0
- package/dist/src/secret-shell-guidance.d.ts +4 -0
- package/dist/src/session-status-class.d.ts +48 -0
- package/dist/src/shared/reserved-env.d.ts +3 -0
- package/dist/src/sibling-agent-node.d.ts +23 -0
- package/dist/src/stale-socket.d.ts +118 -0
- package/dist/src/start-resource-gate.d.ts +146 -0
- package/dist/src/stopped-receipt.d.ts +8 -0
- package/dist/src/subcommand-redirect.d.ts +26 -0
- package/dist/src/task-trace.d.ts +33 -0
- package/dist/src/tmux-attach.d.ts +11 -0
- package/dist/src/tmux-capability.d.ts +58 -0
- package/dist/src/tmux-exact-target.d.ts +36 -0
- package/dist/src/tmux-format.d.ts +20 -0
- package/dist/src/tmux-pane-prompt.d.ts +22 -0
- package/dist/src/tmux.d.ts +44 -0
- package/dist/src/token-cli.d.ts +13 -0
- package/dist/src/tool-allowlist.d.ts +28 -0
- package/dist/src/unsafe-package-path-reason.d.ts +31 -0
- package/dist/src/upgrade-channel.d.ts +31 -0
- package/dist/src/win-launcher.d.ts +20 -0
- package/dist/src/windows-codex-copresence.d.ts +60 -0
- package/package.json +14 -7
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/** Per-node record, written into the node dir next to the other state files. */
|
|
2
|
+
export declare const CODEX_AUTH_FINGERPRINT_FILE = ".codex-auth-fingerprint.json";
|
|
3
|
+
/**
|
|
4
|
+
* Host-wide index of the same records — the read side's source of truth.
|
|
5
|
+
*
|
|
6
|
+
* Under `~/.anet/` because that is already this repo's host-scoped state
|
|
7
|
+
* directory (global config lives there) and it does not move with `cwd`, which
|
|
8
|
+
* is the entire point: the nodes that need to see each other are in different
|
|
9
|
+
* workspaces.
|
|
10
|
+
*/
|
|
11
|
+
export declare function codexFingerprintIndexDir(home?: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Index filename for a node.
|
|
14
|
+
*
|
|
15
|
+
* 🔴 Derived from the node's DIRECTORY, never its alias. Aliases are not unique
|
|
16
|
+
* across workspaces — the reference fleet runs a node beside its own `2号`
|
|
17
|
+
* twin, and nothing stops two workspaces from using one name — so an
|
|
18
|
+
* alias-keyed file would let one node silently overwrite another's record,
|
|
19
|
+
* re-creating the very blindness this index fixes. A directory cannot be two
|
|
20
|
+
* nodes.
|
|
21
|
+
*
|
|
22
|
+
* 🔴 And derived from the directory rather than from `node_id`, even though the
|
|
23
|
+
* CLI has one handy: agent-node's `NODE_ID` can be empty (older configs), so
|
|
24
|
+
* a node_id key would need a fallback — and then the same node would write
|
|
25
|
+
* one filename from one launch path and a different one from the other,
|
|
26
|
+
* leaving two records that look like two nodes sharing a credential. One
|
|
27
|
+
* deterministic key from an input both call sites already have is worth more
|
|
28
|
+
* than a prettier name. `node_id` is still recorded, for humans.
|
|
29
|
+
*/
|
|
30
|
+
export declare function codexFingerprintIndexFile(nodeDir: string): string;
|
|
31
|
+
/** Length of the stored fingerprint. Collisions at 8 hex are ~1 in 4 billion;
|
|
32
|
+
* a false "these two share a login" warning costs a second look, while the
|
|
33
|
+
* value staying short keeps it obviously non-reversible in logs. */
|
|
34
|
+
export declare const FINGERPRINT_LENGTH = 8;
|
|
35
|
+
/** Access tokens measured on the reference fleet: `exp - iat` was exactly this,
|
|
36
|
+
* on 29 of 29 samples, with no jitter. Used only to phrase "how long until the
|
|
37
|
+
* shared copy dies" when we have an `exp` but no `iat`. */
|
|
38
|
+
export declare const ACCESS_TOKEN_LIFETIME_SECONDS = 864000;
|
|
39
|
+
/** Past this, a published record's age is shown beside the alias. It does not
|
|
40
|
+
* suppress the warning: a node that has not started in a week still holds the
|
|
41
|
+
* shared credential. See the liveness note on `checkCodexCredentialSharing`. */
|
|
42
|
+
export declare const STALE_RECORD_NOTICE_MS: number;
|
|
43
|
+
export interface CodexAuthFingerprintRecord {
|
|
44
|
+
readonly schema_version: 1 | 2 | 3;
|
|
45
|
+
readonly alias: string;
|
|
46
|
+
/** sha256(refresh_token) truncated — never the token. */
|
|
47
|
+
readonly fingerprint: string;
|
|
48
|
+
readonly written_at: string;
|
|
49
|
+
/** v2: when this copy's ACCESS token expires, ISO-8601, or null if unreadable.
|
|
50
|
+
* A timestamp, not a credential. Two nodes holding one copied file share this
|
|
51
|
+
* instant exactly — which is how "they stop together" becomes observable. */
|
|
52
|
+
readonly access_expires_at?: string | null;
|
|
53
|
+
/** v2: the CODEX_HOME this record was read from. Distinguishes "two copies of
|
|
54
|
+
* one credential" from "two nodes pointed at one directory", which need
|
|
55
|
+
* different fixes. */
|
|
56
|
+
readonly codex_home?: string | null;
|
|
57
|
+
/** v3: the node directory this record describes, canonical. Three jobs: it is
|
|
58
|
+
* the identity used to de-duplicate the index against the sibling scan and to
|
|
59
|
+
* exclude self; and it is the liveness test — if this directory is gone, the
|
|
60
|
+
* node is gone and the record must not accuse anyone. */
|
|
61
|
+
readonly node_dir?: string;
|
|
62
|
+
/** v3: recorded for humans reading the index by hand. Never the key: it can be
|
|
63
|
+
* absent, and a key that is sometimes absent produces two records per node. */
|
|
64
|
+
readonly node_id?: string | null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Hash the refresh token out of an auth.json body.
|
|
68
|
+
*
|
|
69
|
+
* Accepts both shapes seen in the wild: the current codex layout nests it
|
|
70
|
+
* under `tokens` (`{auth_mode, tokens:{id_token, access_token, refresh_token,
|
|
71
|
+
* account_id}, last_refresh}`), and older/plain bodies put `refresh_token` at
|
|
72
|
+
* the top level. Anything else — unparsable, absent, empty, non-string —
|
|
73
|
+
* returns null, because "we cannot tell" must not be reported as "no
|
|
74
|
+
* collision".
|
|
75
|
+
*/
|
|
76
|
+
export declare function fingerprintRefreshToken(authJsonText: string): string | null;
|
|
77
|
+
/**
|
|
78
|
+
* When this copy's access token expires, read out of the JWT's own payload.
|
|
79
|
+
*
|
|
80
|
+
* The signature is NOT verified and nothing here authenticates: we are reading
|
|
81
|
+
* a timestamp the token states about itself, the same way `jwt.io` would. A
|
|
82
|
+
* token that is not a JWT, or carries no numeric `exp`, yields null — unknown
|
|
83
|
+
* stays unknown.
|
|
84
|
+
*/
|
|
85
|
+
export declare function accessTokenExpiry(authJsonText: string): Date | null;
|
|
86
|
+
export interface NodeFingerprint {
|
|
87
|
+
readonly alias: string;
|
|
88
|
+
/** null when the node has no auth.json, or one we could not read. */
|
|
89
|
+
readonly fingerprint: string | null;
|
|
90
|
+
/** When the record was written; used to say how stale a claim is. */
|
|
91
|
+
readonly writtenAt?: string | null;
|
|
92
|
+
/** When this copy's access token expires, if we could read it. */
|
|
93
|
+
readonly accessExpiresAt?: Date | null;
|
|
94
|
+
/** The CODEX_HOME this fingerprint came from, if known. */
|
|
95
|
+
readonly codexHome?: string | null;
|
|
96
|
+
/** Canonical node directory — identity for de-duplication and self-exclusion. */
|
|
97
|
+
readonly nodeDir?: string | null;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Other nodes holding the same credential chain as `self`.
|
|
101
|
+
*
|
|
102
|
+
* Records with no fingerprint are skipped rather than grouped: "unknown" is
|
|
103
|
+
* not a match.
|
|
104
|
+
*
|
|
105
|
+
* 🔴 Self is excluded by node DIRECTORY when both sides carry one, and only
|
|
106
|
+
* falls back to alias for pre-v3 records. Once the read side is host-wide,
|
|
107
|
+
* alias stops being an identity: two workspaces may each hold a node called
|
|
108
|
+
* the same thing, and excluding by name would hide a genuine collision
|
|
109
|
+
* between them — the exact failure this index was added to fix.
|
|
110
|
+
*/
|
|
111
|
+
export declare function collidingNodes(records: readonly NodeFingerprint[], self: NodeFingerprint): NodeFingerprint[];
|
|
112
|
+
/**
|
|
113
|
+
* The warning an operator sees at start. Returns [] when nothing collides —
|
|
114
|
+
* callers print exactly what they get, so silence is representable and the
|
|
115
|
+
* "no collision" path cannot accidentally print a scary empty banner.
|
|
116
|
+
*
|
|
117
|
+
* Deliberately a warning and never a refusal: every node on a host shares one
|
|
118
|
+
* login today, so refusing to start would take the whole box down at once
|
|
119
|
+
* over a condition that is survivable until the next refresh.
|
|
120
|
+
*
|
|
121
|
+
* 🔴 Two outcomes, and the wrong one is actively misleading. The default text
|
|
122
|
+
* ("whichever node refreshes first keeps working") assumes refresh WORKS.
|
|
123
|
+
* On a host that cannot reach the OAuth token endpoint — egress blocked, or
|
|
124
|
+
* a proxy that allowlists the model API and 403s everything else — nobody
|
|
125
|
+
* refreshes, and the nodes holding one copy do not degrade one at a time:
|
|
126
|
+
* they stop together the moment the shared access token expires. Field case:
|
|
127
|
+
* three nodes whose auth.json files were byte-identical, carrying one access
|
|
128
|
+
* token, all expired at the same instant.
|
|
129
|
+
*
|
|
130
|
+
* We do NOT probe the network to tell these apart — a start path is the wrong
|
|
131
|
+
* place to open a socket, and a probe would answer for this moment rather
|
|
132
|
+
* than for the window that matters. We branch on evidence already in the
|
|
133
|
+
* file: an access token whose `exp` has passed is a copy that demonstrably
|
|
134
|
+
* stopped being refreshed, which is the observable the field case exhibits.
|
|
135
|
+
* When `exp` is still in the future we cannot prove reachability either way,
|
|
136
|
+
* and the default wording is the right one to print.
|
|
137
|
+
*/
|
|
138
|
+
export declare function sharedCredentialWarningLines(self: NodeFingerprint, colliding: readonly NodeFingerprint[], now?: Date): string[];
|
|
139
|
+
/**
|
|
140
|
+
* Two different things both read as "codex cannot refresh", and they need
|
|
141
|
+
* opposite remedies. Telling them apart is the whole value of this mapping:
|
|
142
|
+
* treating a transport failure as a rotation conflict sends an operator to
|
|
143
|
+
* copy someone else's auth.json, which is precisely how a shared-credential
|
|
144
|
+
* chain gets created.
|
|
145
|
+
*/
|
|
146
|
+
export type CodexRefreshFailureKind =
|
|
147
|
+
/** Someone else spent this refresh token first. */
|
|
148
|
+
"rotation-conflict"
|
|
149
|
+
/** The token endpoint is unreachable — nobody's token is rotating. */
|
|
150
|
+
| "token-endpoint-unreachable";
|
|
151
|
+
export interface CodexRefreshFailure {
|
|
152
|
+
readonly kind: CodexRefreshFailureKind;
|
|
153
|
+
readonly lines: string[];
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Name what actually went wrong with a codex refresh, and say what to do.
|
|
157
|
+
*
|
|
158
|
+
* Returns null for anything else, so callers fall through to whatever they
|
|
159
|
+
* printed before — an unrelated error must pass through unchanged.
|
|
160
|
+
*
|
|
161
|
+
* Order matters: a reuse rejection is a server answer and is checked first; a
|
|
162
|
+
* transport failure means no answer arrived at all.
|
|
163
|
+
*/
|
|
164
|
+
export declare function describeCodexRefreshFailure(text: string, colliding?: readonly NodeFingerprint[]): CodexRefreshFailure | null;
|
|
165
|
+
export interface CodexCredentialSharingCheck {
|
|
166
|
+
/** This node's own directory — where its record is written. */
|
|
167
|
+
readonly nodeDir: string;
|
|
168
|
+
/** Shown to the operator; also how a node recognises its own record. */
|
|
169
|
+
readonly alias: string;
|
|
170
|
+
/** The CODEX_HOME whose auth.json this node will actually use. */
|
|
171
|
+
readonly codexHome: string;
|
|
172
|
+
/** Recorded for humans reading the index; never used as a key. Optional
|
|
173
|
+
* because agent-node's can be empty on older configs. */
|
|
174
|
+
readonly nodeId?: string | null;
|
|
175
|
+
/** Where the warning goes. Defaults to stderr. */
|
|
176
|
+
readonly say?: (message: string) => void;
|
|
177
|
+
/** Injected for tests; production passes nothing. */
|
|
178
|
+
readonly now?: Date;
|
|
179
|
+
/** Injected for tests; production uses the real host index under `~/.anet`. */
|
|
180
|
+
readonly indexDir?: string;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* The whole operation: read this node's credential, publish an 8-hex record,
|
|
184
|
+
* read what the *other* nodes published, and warn if any of them is on the
|
|
185
|
+
* same chain. Returns the colliding nodes so a caller can name them again if
|
|
186
|
+
* codex later fails to refresh.
|
|
187
|
+
*
|
|
188
|
+
* 🔴 Reads the neighbours' published fingerprint files, never their
|
|
189
|
+
* credentials. A guard against credential sharing must not itself become a
|
|
190
|
+
* reason to open every node's auth.json.
|
|
191
|
+
*
|
|
192
|
+
* 🔴 Always recomputes. The fingerprint changes every time codex refreshes in
|
|
193
|
+
* place, so a stale record is a wrong answer; both call paths may run for
|
|
194
|
+
* one node in sequence, and whichever runs last must leave the current
|
|
195
|
+
* value, never an older one.
|
|
196
|
+
*
|
|
197
|
+
* 🔴 Liveness is EXISTENCE, not age. An index record outlives the node that
|
|
198
|
+
* wrote it, and a stale one accusing a node that no longer exists is worse
|
|
199
|
+
* than silence — it teaches operators to ignore the warning. So a record
|
|
200
|
+
* whose `node_dir` is gone is dropped and its index entry deleted.
|
|
201
|
+
* Age deliberately does NOT filter: on the reference fleet two of the three
|
|
202
|
+
* nodes in the worst-affected group were not running at all, and six of 29
|
|
203
|
+
* credential files had not been touched in days, while still holding the
|
|
204
|
+
* shared copy. An age cutoff would have hidden precisely the collisions
|
|
205
|
+
* worth reporting. Age is surfaced in the warning instead, so the operator
|
|
206
|
+
* can judge how old the claim is.
|
|
207
|
+
*
|
|
208
|
+
* Never throws and never refuses: observability must not be the reason a node
|
|
209
|
+
* will not start.
|
|
210
|
+
*/
|
|
211
|
+
export declare function checkCodexCredentialSharing(opts: CodexCredentialSharingCheck): NodeFingerprint[];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** The node's pinned binary from config.json, or undefined when not set. */
|
|
2
|
+
export declare function configuredCodexBin(profile: {
|
|
3
|
+
codexBin?: unknown;
|
|
4
|
+
} | null | undefined): string | undefined;
|
|
5
|
+
/** The node's pinned version from config.json, or undefined when not set. */
|
|
6
|
+
export declare function configuredCodexVersion(profile: {
|
|
7
|
+
codexVersion?: unknown;
|
|
8
|
+
} | null | undefined): string | undefined;
|
|
9
|
+
/** Order: --codex-bin (one-off) > config.json codexBin > bare "codex" (main's default). */
|
|
10
|
+
export declare function resolveCopresenceCodexBin(flag: string | undefined, profile: {
|
|
11
|
+
codexBin?: unknown;
|
|
12
|
+
} | null | undefined): string;
|
|
13
|
+
/**
|
|
14
|
+
* Null = the binary reports the pinned version. Otherwise the lines to print before
|
|
15
|
+
* refusing to start. An unreadable version is a refusal too: a pin that cannot be
|
|
16
|
+
* checked is not honoured silently.
|
|
17
|
+
*/
|
|
18
|
+
export declare function codexVersionPinMismatch(input: {
|
|
19
|
+
expected: string;
|
|
20
|
+
actual: string | null;
|
|
21
|
+
codexBin: string;
|
|
22
|
+
displayName: string;
|
|
23
|
+
}): string[] | null;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const COMMHUB_TOOLS_APPROVAL_KEY = "mcp_servers.commhub.default_tools_approval_mode";
|
|
2
|
+
export declare const COMMHUB_TOOLS_APPROVAL_VALUE = "approve";
|
|
3
|
+
export declare const COMMHUB_TOKEN_ENV = "ANET_CODEX_COMMHUB_TOKEN";
|
|
4
|
+
/**
|
|
5
|
+
* `-c` fragments (without the `-c` flags) for the commhub MCP server.
|
|
6
|
+
* - `quoted`: TOML string literals, for the POSIX launcher (each fragment is shellQuoted).
|
|
7
|
+
* - `bare`: unquoted values, for the Windows launcher (cmd.exe mangles `"`; codex parses a
|
|
8
|
+
* value that is not valid TOML as a plain string, so `x=approve` reads as "approve").
|
|
9
|
+
*/
|
|
10
|
+
export declare function codexCommhubMcpOverrides(hubBaseUrl: string, style: "quoted" | "bare"): string[];
|
|
11
|
+
/**
|
|
12
|
+
* The complete argv of the Windows co-presence `codex app-server` (the launcher passes
|
|
13
|
+
* exactly this to windowsManagedProcess). Pure, so it is testable on Linux CI where the
|
|
14
|
+
* win32 launcher itself cannot run (#720 review: assert the launcher's real argv).
|
|
15
|
+
*/
|
|
16
|
+
export declare function codexWindowsAppServerArgs(o: {
|
|
17
|
+
approvalPolicy: string;
|
|
18
|
+
sandboxMode: string;
|
|
19
|
+
model: string;
|
|
20
|
+
hub: string;
|
|
21
|
+
wsUrl: string;
|
|
22
|
+
}): string[];
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #535 (audit P1-2) — keep the codex co-presence bridge's last words.
|
|
3
|
+
*
|
|
4
|
+
* The bridge (piece ②) runs in its own tmux session. When it failed, that
|
|
5
|
+
* session exited, and the launcher still printed
|
|
6
|
+
* Debug: tmux attach -t '=<alias>-桥'
|
|
7
|
+
* which pointed at nothing. The audit only found the real cause
|
|
8
|
+
* (`exact paired package identity validation failed: … unsafe ownership or
|
|
9
|
+
* mode`) by re-running the bridge command by hand.
|
|
10
|
+
*
|
|
11
|
+
* Its stdout/stderr now also go to `<node dir>/codex-bridge.log`, and a failed
|
|
12
|
+
* start prints the tail of that file plus the path. Same idea as the OpenCode
|
|
13
|
+
* twin (copresence-bridge.log), with two differences:
|
|
14
|
+
* - the file is NOT under logs/: `anet logs` shows the reverse-sorted first
|
|
15
|
+
* `*.log` there, and a name like this sorts above agent-node's dated logs,
|
|
16
|
+
* so it would hide them;
|
|
17
|
+
* - the copy is capped (the bridge runs for days and agent-node already
|
|
18
|
+
* writes its own dated log). Past the cap the remainder is drained, never
|
|
19
|
+
* refused: a reader that stopped reading would SIGPIPE `tee` and with it
|
|
20
|
+
* the bridge's stdout.
|
|
21
|
+
*/
|
|
22
|
+
export declare const CODEX_BRIDGE_LOG_NAME = "codex-bridge.log";
|
|
23
|
+
export declare const CODEX_BRIDGE_LOG_CAP_BYTES: number;
|
|
24
|
+
export declare const CODEX_BRIDGE_TAIL_LINES = 20;
|
|
25
|
+
/**
|
|
26
|
+
* A bash statement that sends the rest of the script's stdout+stderr to the
|
|
27
|
+
* pane AND to the log (whole lines, up to about CAP bytes, flushed line by line). `quotedLogPath` must already be
|
|
28
|
+
* shell-quoted. The parent `exec`s anet next, so anet's pid is still the pane's
|
|
29
|
+
* process; `tee`/`awk` inherit the session's ANET_NODE_MARKER and are
|
|
30
|
+
* reaped with it on stop.
|
|
31
|
+
*/
|
|
32
|
+
export declare function codexBridgeTeeCommand(quotedLogPath: string, capBytes?: number): string;
|
|
33
|
+
/** Last `n` non-empty lines, with trailing whitespace trimmed. */
|
|
34
|
+
export declare function tailLines(text: string, n?: number): string[];
|
|
35
|
+
export interface BridgeFailureInput {
|
|
36
|
+
readonly bridgeAlive: boolean;
|
|
37
|
+
readonly attachCommand: string;
|
|
38
|
+
readonly logPath: string;
|
|
39
|
+
readonly logText: string;
|
|
40
|
+
readonly waitedSeconds: number;
|
|
41
|
+
readonly cleanupCommand: string;
|
|
42
|
+
}
|
|
43
|
+
/** What the launcher prints when the bridge did not report ready. */
|
|
44
|
+
export declare function bridgeLaunchFailureLines(i: BridgeFailureInput): string[];
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Merge user config.env into one co-presence stage. Required launcher values
|
|
2
|
+
* always win. Invalid shell names/NULs fail closed before a tmux session starts. */
|
|
3
|
+
export declare function codexCopresenceStageEnv(configEnv: Readonly<Record<string, string>>, required: Readonly<Record<string, string>>): Record<string, string>;
|
|
4
|
+
/** Contents for a private source-then-delete file. Values never enter argv. */
|
|
5
|
+
export declare function codexCopresenceEnvFileText(env: Readonly<Record<string, string>>): string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type CodexHomeLoginState = "logged-in" | "needs-login" | "unknown";
|
|
2
|
+
export interface CodexHomeLoginVerdict {
|
|
3
|
+
readonly state: CodexHomeLoginState;
|
|
4
|
+
/** One phrase for the operator: why this state. No secrets. */
|
|
5
|
+
readonly reason: string;
|
|
6
|
+
}
|
|
7
|
+
/** The node's verdict. auth.json is judged by the #529 helper; see the header for the two fail-open cases. */
|
|
8
|
+
export declare function codexHomeLoginState(codexHome: string, env?: Record<string, string | undefined>): CodexHomeLoginVerdict;
|
|
9
|
+
/** Exit code for `needs-login`: distinct from 1 (failed) so scripts can tell "do something" from "broken". */
|
|
10
|
+
export declare const NEEDS_LOGIN_EXIT_CODE = 3;
|
|
11
|
+
/** What the launcher prints instead of "✅ 就绪": a state line, then the #529 next step. Nothing executed. */
|
|
12
|
+
export declare function describeCodexNeedsLogin(i: {
|
|
13
|
+
alias: string;
|
|
14
|
+
codexHome: string;
|
|
15
|
+
reason: string;
|
|
16
|
+
}): string[];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #512 — which model a codex co-presence start runs on, and why.
|
|
3
|
+
*
|
|
4
|
+
* Order: explicit `--model` flag > the node's own config (`model` in
|
|
5
|
+
* .anet/nodes/<id>/config.json, written by `anet node create --model` /
|
|
6
|
+
* `anet node edit --model`) > DEFAULT_CODEX_MODEL.
|
|
7
|
+
*
|
|
8
|
+
* The bug this replaces: the start path computed `opts.model || DEFAULT`
|
|
9
|
+
* where `opts.model` was ONLY the CLI flag, so every start that did not
|
|
10
|
+
* repeat `--model` — the plain `anet node start <name>`, the boot sweep, and
|
|
11
|
+
* `anet node codex start|restart|resume` (which re-invoke `node start`
|
|
12
|
+
* without `--model`) — silently put the node on the default.
|
|
13
|
+
*
|
|
14
|
+
* One resolved value feeds all three pieces (app-server `-c model=`, the
|
|
15
|
+
* thread/resume `model` override, TUI `-m`), so they cannot disagree.
|
|
16
|
+
*/
|
|
17
|
+
export type CodexModelSource = "flag" | "node-config" | "default";
|
|
18
|
+
export interface ResolvedCodexModel {
|
|
19
|
+
model: string;
|
|
20
|
+
source: CodexModelSource;
|
|
21
|
+
}
|
|
22
|
+
export declare function resolveCodexCopresenceModel(flagModel: unknown, nodeConfigModel: unknown, fallback?: string): ResolvedCodexModel;
|
|
23
|
+
export declare function describeCodexModelSource(r: ResolvedCodexModel): string;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export interface CodexHomeInheritedFile {
|
|
2
|
+
readonly name: string;
|
|
3
|
+
readonly mode: number;
|
|
4
|
+
/** Why a node needs it — printed when staged, so the action is legible. */
|
|
5
|
+
readonly because: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const CODEX_HOME_INHERITED_FILES: readonly CodexHomeInheritedFile[];
|
|
8
|
+
export interface StatLike {
|
|
9
|
+
readonly mtimeMs: number;
|
|
10
|
+
}
|
|
11
|
+
export type StatFn = (path: string) => StatLike | null;
|
|
12
|
+
export interface CodexHomeStageStep {
|
|
13
|
+
readonly name: string;
|
|
14
|
+
readonly src: string;
|
|
15
|
+
readonly dst: string;
|
|
16
|
+
readonly mode: number;
|
|
17
|
+
readonly reason: "missing" | "host-newer";
|
|
18
|
+
readonly because: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* What to copy from the host CODEX_HOME into the node's, right now.
|
|
22
|
+
*
|
|
23
|
+
* Pure: takes the stat function so the ordering rules are testable without a
|
|
24
|
+
* filesystem. A node file that is NEWER than the host's is left alone — codex
|
|
25
|
+
* refreshes its own copy in place, and clobbering it would throw away a fresher
|
|
26
|
+
* token than the one we hold.
|
|
27
|
+
*/
|
|
28
|
+
export declare function codexHomeStagePlan(hostHome: string, nodeHome: string, stat: StatFn, join: (a: string, b: string) => string, files?: readonly CodexHomeInheritedFile[]): CodexHomeStageStep[];
|
|
29
|
+
export type CodexTuiBlocker = "sign-in" | "update-prompt" | "trust-prompt" | "input-not-ready";
|
|
30
|
+
export type CodexTuiPaneState = CodexTuiBlocker | "not-painted" | "usable";
|
|
31
|
+
export interface CodexKnownStartupPromptAction {
|
|
32
|
+
readonly kind: "keep-existing-model";
|
|
33
|
+
readonly key: "2";
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Return the one startup action whose exact current-screen contract we know.
|
|
37
|
+
*
|
|
38
|
+
* Codex's model-migration picker defaults to "Try new model" and both Enter
|
|
39
|
+
* and Escape confirm that highlighted choice. Sending a generic confirmation
|
|
40
|
+
* would therefore silently change the node's configured model. The explicit
|
|
41
|
+
* `2` choice preserves the current model and acknowledges this exact notice.
|
|
42
|
+
*/
|
|
43
|
+
export declare function codexKnownStartupPromptAction(onScreen: string): CodexKnownStartupPromptAction | null;
|
|
44
|
+
/**
|
|
45
|
+
* Classify the TUI, from two different reads of the same pane.
|
|
46
|
+
*
|
|
47
|
+
* They answer different questions and need different tmux flags — the same
|
|
48
|
+
* distinction `waitForTmuxPaneText` documents one screenful up:
|
|
49
|
+
*
|
|
50
|
+
* onScreen (capture-pane, no -S) → "is it sitting on a prompt right now"
|
|
51
|
+
* A prompt that was already answered stays in scrollback forever,
|
|
52
|
+
* so reading history here would report a fixed node as blocked.
|
|
53
|
+
*
|
|
54
|
+
* everSeen (capture-pane -S -N) → "did the TUI ever paint"
|
|
55
|
+
* The banner is printed once and scrolls away — an MCP error burst
|
|
56
|
+
* was enough to push it off screen in under a minute — so reading
|
|
57
|
+
* only the current screen here would report a working node as dead.
|
|
58
|
+
*
|
|
59
|
+
* 🔴 And "the pane has text" answers neither. An earlier version of this used
|
|
60
|
+
* "≥3 non-empty lines ⇒ ready" and passed a node parked on the sign-in page.
|
|
61
|
+
* Measured timeline for that start:
|
|
62
|
+
*
|
|
63
|
+
* t≈3s 22 non-empty lines ← launcher output, TUI not up yet
|
|
64
|
+
* t≈6s 0 non-empty lines ← codex clears the screen and takes over
|
|
65
|
+
* t≈9s 10 non-empty lines ← the sign-in page finally paints
|
|
66
|
+
*
|
|
67
|
+
* The rule fired at t≈3s, six seconds before the thing it was meant to catch
|
|
68
|
+
* existed.
|
|
69
|
+
*/
|
|
70
|
+
export declare function codexTuiPaneState(onScreen: string, everSeen?: string): CodexTuiPaneState;
|
|
71
|
+
/**
|
|
72
|
+
* What the operator should read when a node came up but cannot work. Names the
|
|
73
|
+
* blocker AND where to look — a launcher that only says "not ready" sends
|
|
74
|
+
* someone hunting through three tmux sessions.
|
|
75
|
+
*/
|
|
76
|
+
export declare function describeCodexTuiBlocker(blocker: CodexTuiBlocker, displayName: string, tuiSession: string): string;
|
|
77
|
+
/** The TUI never painted at all — a distinct failure from being blocked. */
|
|
78
|
+
export declare function describeCodexTuiNotPainted(displayName: string, tuiSession: string, waitedMs: number): string;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
export interface CodexCopresenceProfileFields {
|
|
2
|
+
readonly runtime?: string;
|
|
3
|
+
readonly codexCopresence?: boolean;
|
|
4
|
+
readonly codexCopresenceFullAccess?: boolean;
|
|
5
|
+
readonly flags?: Record<string, unknown>;
|
|
6
|
+
/** Absolute project directory to launch the codex TUI in. Passed as
|
|
7
|
+
* `-C <dir>` to `codex resume`. Also cross-checked against hub-reported
|
|
8
|
+
* `session.project_dir` in Phase 4.3. */
|
|
9
|
+
readonly codexProjectDir?: string;
|
|
10
|
+
/** Which launch adapter to use in Phase 3.2. `"codex-standard"` = stock
|
|
11
|
+
* `codex app-server` + `codex resume`; `"codex-custom-wrapper"` = partner-team-class
|
|
12
|
+
* custom LLM API wrapper (hosted tool / compaction / image-output compat).
|
|
13
|
+
* Unknown / absent → Phase 0 fail-closed (unknown adapters must not fall
|
|
14
|
+
* through to a default that might not fit the node's real topology). */
|
|
15
|
+
readonly codexLaunchAdapter?: "codex-standard" | "codex-custom-wrapper";
|
|
16
|
+
/** Alias of the peer that will verify this node's identity in Phase 6.
|
|
17
|
+
* Peer MUST be (a) registered in the same network, (b) online at resume
|
|
18
|
+
* time, (c) not this node itself. Identity attestation uses a fresh
|
|
19
|
+
* Hub outbound `send_task` from THIS node's Bridge to <codexProbePeer>
|
|
20
|
+
* carrying a nonce; peer echoes the nonce back; CLI cross-checks Hub's
|
|
21
|
+
* recorded `from_name / from_node_id / to_name / to_node_id`.
|
|
22
|
+
*
|
|
23
|
+
* 🔴 self-loop is deliberately not accepted here — it cannot prove
|
|
24
|
+
* cross-node routing closure (design v7 Phase 6 = D, per the partner team
|
|
25
|
+
* 06cfb29a). Single-node users cannot run `anet node resume` in
|
|
26
|
+
* this initial cut; tracked at #1527. */
|
|
27
|
+
readonly codexProbePeer?: string;
|
|
28
|
+
}
|
|
29
|
+
/** #1521 — the three fields `anet node resume <alias>` will require on a
|
|
30
|
+
* codex-copresence node once the resume dispatcher lands (#1528, Draft).
|
|
31
|
+
* In this PR, only shape-level enumeration exists (see
|
|
32
|
+
* `missingCodexResumeFields` below); no CLI code reads that enumeration
|
|
33
|
+
* and the operator-facing remediation flow is deliberately deferred
|
|
34
|
+
* along with its real caller. */
|
|
35
|
+
export type CodexResumeRequiredField = "codexProjectDir" | "codexLaunchAdapter" | "codexProbePeer";
|
|
36
|
+
export declare const CODEX_RESUME_REQUIRED_FIELDS: readonly CodexResumeRequiredField[];
|
|
37
|
+
/** Enumerate which of the three resume-required fields are absent /
|
|
38
|
+
* wrong-shaped on this profile. Returns [] when all three are present
|
|
39
|
+
* and well-shaped.
|
|
40
|
+
*
|
|
41
|
+
* 🔴 SHAPE-ONLY validation:
|
|
42
|
+
* - codexProjectDir : string starting with `/`. This is a CHEAP typo
|
|
43
|
+
* filter, NOT the security gate. Phase 0 (in #1528) MUST additionally
|
|
44
|
+
* `realpath`/canonicalize + reject NUL, `/`, and any untrusted target
|
|
45
|
+
* before passing this value to `codex resume -C <dir>` (per the partner team
|
|
46
|
+
* 4bab8196: "startsWith('/') 不是最终安全门").
|
|
47
|
+
* - codexLaunchAdapter : enum membership.
|
|
48
|
+
* - codexProbePeer : Hub exact-alias lookup, so value MUST equal its own
|
|
49
|
+
* trim. Values with leading/trailing whitespace would fail Hub lookup
|
|
50
|
+
* 100% of the time; rejecting them at the shape layer is a witnessed-red
|
|
51
|
+
* canary rather than a permissive pass-through (per the partner team 048d0061 R2:
|
|
52
|
+
* "不要 pin 一个必然在 Hub exact lookup 阶段失败的 well-shaped 值").
|
|
53
|
+
* Liveness (registered + online + non-self) is a separate hub-side
|
|
54
|
+
* check in Phase 0.
|
|
55
|
+
*
|
|
56
|
+
* Deliberately NOT included: an operator-facing diagnostic helper. An
|
|
57
|
+
* earlier draft included `codexResumeMissingConfigHint` that referenced
|
|
58
|
+
* a `anet node config apply` verb which does not exist in the tree —
|
|
59
|
+
* 外部团队 4bab8196 blocked it as the exact "存在 ≠ 会执行" antipattern.
|
|
60
|
+
* The diagnostic lands with its real caller in a later PR that either
|
|
61
|
+
* (a) implements `anet node config apply` first, or (b) reworks the
|
|
62
|
+
* diagnostic to reference existing commands only. */
|
|
63
|
+
export declare function missingCodexResumeFields(profile: CodexCopresenceProfileFields): CodexResumeRequiredField[];
|
|
64
|
+
export declare const CODEX_COPRESENCE_RUNTIME = "codex-app-server";
|
|
65
|
+
/**
|
|
66
|
+
* Should this `anet node start` bring up the co-presence stack?
|
|
67
|
+
*
|
|
68
|
+
* The flag still works for a one-off; the profile field is what makes the
|
|
69
|
+
* second start a single command.
|
|
70
|
+
*/
|
|
71
|
+
export declare function codexCopresenceRequested(flagPassed: boolean, profile: CodexCopresenceProfileFields): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* True when a plain `anet node start` just became enough for this node — i.e.
|
|
74
|
+
* the flag was passed and the profile had not recorded it yet. Callers persist
|
|
75
|
+
* `codexCopresence: true` and tell the user they can drop the flag.
|
|
76
|
+
*/
|
|
77
|
+
export declare function shouldPersistCodexCopresence(flagPassed: boolean, profile: CodexCopresenceProfileFields): boolean;
|
|
78
|
+
export interface CodexCopresencePosture {
|
|
79
|
+
readonly sandboxMode: "danger-full-access" | "read-only";
|
|
80
|
+
readonly approvalPolicy: "never" | "on-request";
|
|
81
|
+
/** Set when the node asked for full access and is NOT getting it. */
|
|
82
|
+
readonly downgradeNotice?: string;
|
|
83
|
+
/** Set when a remembered grant (not this invocation's flag) opened it up. */
|
|
84
|
+
readonly grantedFromProfile: boolean;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Resolve the sandbox the co-presence app-server runs under.
|
|
88
|
+
*
|
|
89
|
+
* Precedence: this invocation's flag → a grant already recorded on the profile
|
|
90
|
+
* → read-only. `flags.sandboxMode` alone never opens the sandbox; it only
|
|
91
|
+
* decides whether staying closed is worth telling the user about.
|
|
92
|
+
*/
|
|
93
|
+
export declare function codexCopresencePosture(dangerFlagPassed: boolean, profile: CodexCopresenceProfileFields, displayName?: string): CodexCopresencePosture;
|
|
94
|
+
/** True when an explicit grant should be written to the profile for next time. */
|
|
95
|
+
export declare function shouldPersistCodexFullAccess(dangerFlagPassed: boolean, profile: CodexCopresenceProfileFields): boolean;
|
|
96
|
+
/**
|
|
97
|
+
* What `anet node create --runtime codex-app-server` should record, so that a
|
|
98
|
+
* later `anet node start <name>` is a single command with no flag.
|
|
99
|
+
*
|
|
100
|
+
* Shape copied from opencode, which already does exactly this at create time:
|
|
101
|
+
*
|
|
102
|
+
* ...(runtime === "opencode-cli"
|
|
103
|
+
* ? { opencodeMode: opts.mode === "copresence" || opts.copresence === "true"
|
|
104
|
+
* ? "copresence" : "headless" }
|
|
105
|
+
* : {}),
|
|
106
|
+
*
|
|
107
|
+
* Deliberately NOT keyed on which alias the operator typed. `codex-tui`,
|
|
108
|
+
* `codex-appserver` and `codex-app-server` are documented as aliases of one
|
|
109
|
+
* runtime (agent-node/src/cli.ts); giving one of them a different default would
|
|
110
|
+
* quietly break that contract, and "I typed the short name" is not a reliable
|
|
111
|
+
* statement of intent. Opting in stays explicit, exactly as it is for opencode.
|
|
112
|
+
*/
|
|
113
|
+
export declare function codexCopresenceCreateFields(normalizedRuntime: string | undefined, copresenceOpt: string | boolean | undefined): {
|
|
114
|
+
codexCopresence?: true;
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* One line at create time for a node that will start headless.
|
|
118
|
+
*
|
|
119
|
+
* NOT keyed on which alias the operator typed: by the time create builds the
|
|
120
|
+
* profile, `opts.runtime` has already been normalized to `codex-app-server`, so
|
|
121
|
+
* `codex-tui` is simply not recoverable here — a hint that tried to read it
|
|
122
|
+
* would never fire. (It didn't; that is how this was found.) Keying on the
|
|
123
|
+
* runtime instead also keeps the aliases true synonyms.
|
|
124
|
+
*/
|
|
125
|
+
export declare function codexCopresenceCreateHint(normalizedRuntime: string | undefined, copresenceOpt: string | boolean | undefined, displayName: string): string | undefined;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export interface CodexRecoveryVerification {
|
|
2
|
+
method: "thread/start" | "thread/resume";
|
|
3
|
+
threadId: string;
|
|
4
|
+
verifiedAt: string;
|
|
5
|
+
historyTurnCount: number;
|
|
6
|
+
historyFingerprint: string;
|
|
7
|
+
/** Metadata-only thread/read proof that Codex resolved this exact rollout. */
|
|
8
|
+
persistedPath?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface CodexRecoveryBackup {
|
|
11
|
+
backupDir: string;
|
|
12
|
+
createdAt: string;
|
|
13
|
+
stateFiles: string[];
|
|
14
|
+
}
|
|
15
|
+
/** Transaction boundary shared by Windows and POSIX cutovers: the snapshot
|
|
16
|
+
* cannot begin until every authoritative state writer has quiesced. */
|
|
17
|
+
export declare function quiesceThenSnapshot<T>(quiesce: () => Promise<void>, snapshot: () => T): Promise<T>;
|
|
18
|
+
/** Copy and hash without ever materializing the whole file in a Buffer.
|
|
19
|
+
* Zero chunks are sought over and the final length is truncated explicitly,
|
|
20
|
+
* so a multi-GiB sparse rollout stays sparse in the private recovery point. */
|
|
21
|
+
export declare function copyAndHashRecoveryFile(source: string, target: string): {
|
|
22
|
+
size: number;
|
|
23
|
+
sha256: string;
|
|
24
|
+
};
|
|
25
|
+
/** A stored thread is never considered resumed until app-server reads the
|
|
26
|
+
* exact thread back with persisted history. This is deliberately stricter
|
|
27
|
+
* than accepting a successful thread/resume RPC response. */
|
|
28
|
+
export declare function verifyCodexThreadHistory(method: "thread/start" | "thread/resume", expectedThreadId: string, readResult: unknown, now?: Date): CodexRecoveryVerification;
|
|
29
|
+
/**
|
|
30
|
+
* #512 — `model` is the node's resolved model. It MUST ride on thread/resume:
|
|
31
|
+
* measured on codex 0.155 (Docker), a resume without it puts the thread back on
|
|
32
|
+
* the model recorded in the rollout's last turn_context and ignores the
|
|
33
|
+
* app-server's `-c model=`. This launcher is the first client to load the
|
|
34
|
+
* thread, and once loaded, later resumes without a model (the bridge's) keep
|
|
35
|
+
* whatever the first one set — so this is the request that decides the model
|
|
36
|
+
* for every subsequent turn: the configured model wins over the thread's
|
|
37
|
+
* recorded one.
|
|
38
|
+
*
|
|
39
|
+
* Returns the model the app-server reports for the resumed thread (if any) so
|
|
40
|
+
* the caller can say which model the session is actually on.
|
|
41
|
+
*/
|
|
42
|
+
export declare function resumeAndVerifyCodexThread(threadId: string, request: (method: string, params: Record<string, unknown>) => Promise<unknown>, model?: string): Promise<CodexRecoveryVerification & {
|
|
43
|
+
resumedModel?: string;
|
|
44
|
+
}>;
|
|
45
|
+
/** Private, node-local recovery point. config-recovery.json is deliberately
|
|
46
|
+
* redacted non-credential audit/recovery metadata, not an identity backup.
|
|
47
|
+
* Identity recovery relies on leaving the original node config and CODEX_HOME
|
|
48
|
+
* in place. Credentials are never copied into this snapshot. */
|
|
49
|
+
export declare function backupCodexRecoveryState(opts: {
|
|
50
|
+
nodeDir: string;
|
|
51
|
+
codexHome: string;
|
|
52
|
+
now?: Date;
|
|
53
|
+
}): CodexRecoveryBackup;
|
|
54
|
+
export declare function codexTopologyAudit(profile: Record<string, any>, nodeDir: string, cwd: string): {
|
|
55
|
+
launchMode: string;
|
|
56
|
+
cwd: string;
|
|
57
|
+
codexHome: string;
|
|
58
|
+
remote: any;
|
|
59
|
+
threadId: any;
|
|
60
|
+
model: any;
|
|
61
|
+
flags: any;
|
|
62
|
+
lastRecoveryVerification: any;
|
|
63
|
+
lastRecoveryBackup: {
|
|
64
|
+
createdAt: any;
|
|
65
|
+
stateFiles: any;
|
|
66
|
+
} | null;
|
|
67
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare const CODEX_RESUME_TIMEOUT_ENV = "ANET_CODEX_RESUME_TIMEOUT_MS";
|
|
2
|
+
export declare const CODEX_RESUME_BASE_TIMEOUT_MS = 300000;
|
|
3
|
+
export declare const CODEX_RESUME_PER_GIB_MS = 120000;
|
|
4
|
+
export declare const CODEX_RESUME_MAX_TIMEOUT_MS: number;
|
|
5
|
+
export declare const CODEX_RECOVERY_MIN_PAYLOAD_BYTES: number;
|
|
6
|
+
export declare const CODEX_RECOVERY_PAYLOAD_OVERHEAD_BYTES: number;
|
|
7
|
+
export declare const CODEX_RECOVERY_MAX_PAYLOAD_BYTES: number;
|
|
8
|
+
export interface CopresenceResumeBudget {
|
|
9
|
+
timeoutMs: number;
|
|
10
|
+
rolloutBytes: number | null;
|
|
11
|
+
}
|
|
12
|
+
/** `--new-session` is authoritative for co-presence too: never feed the
|
|
13
|
+
* recorded thread into thread/resume when the operator explicitly asked for
|
|
14
|
+
* a fresh conversation. */
|
|
15
|
+
export declare function codexThreadIdForStart(recordedThreadId: string | undefined, newSession: boolean): string | undefined;
|
|
16
|
+
/** Resolve the recovery deadline once, before opening the app-server socket.
|
|
17
|
+
* The automatic budget is 300 s plus 120 s for each started GiB of the exact
|
|
18
|
+
* recorded rollout, capped at 15 minutes. An explicit environment value wins. */
|
|
19
|
+
export declare function resolveCopresenceResumeBudget(codexHome: string, threadId: string | undefined, env?: NodeJS.ProcessEnv, warn?: (message: string) => void): CopresenceResumeBudget;
|
|
20
|
+
export declare function resolveCopresenceResumeTimeoutMs(codexHome: string, threadId: string | undefined, env?: NodeJS.ProcessEnv, warn?: (message: string) => void): number;
|
|
21
|
+
export declare function formatCopresenceRolloutSize(bytes: number | null): string;
|
|
22
|
+
/** A finite receive ceiling for the one large thread/resume frame emitted by
|
|
23
|
+
* older Codex app-servers. Twice the rollout plus protocol headroom covers the
|
|
24
|
+
* measured JSON expansion without giving the loopback peer an unlimited heap. */
|
|
25
|
+
export declare function resolveCopresenceMaxPayloadBytes(rolloutBytes: number | null): number;
|
|
26
|
+
/** The bridge can make one retry after a resume timeout, so launcher readiness
|
|
27
|
+
* covers both bounded attempts instead of killing it at the old fixed 25 s. */
|
|
28
|
+
export declare function resolveCopresenceBridgeAttachTimeoutMs(resumeTimeoutMs: number): number;
|