inter-agent-bridge-cli 0.2.0

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Inter-Agent Bridge contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/PROTOCOL.md ADDED
@@ -0,0 +1,65 @@
1
+ # Inter-Agent Bridge Protocol v0.2.0
2
+
3
+ ## Purpose
4
+ Define a lightweight, local-first standard for reading and coordinating cross-agent session evidence across Codex, Gemini, and Claude.
5
+
6
+ ## Tenets
7
+ 1. Local-first: read local session logs only by default.
8
+ 2. Evidence-based: every claim must map to source sessions.
9
+ 3. Context-light: return concise structured output first.
10
+ 4. Dual implementation parity: Node and Rust must follow the same command and JSON contract.
11
+
12
+ ## Canonical Modes
13
+ - `verify`
14
+ - `steer`
15
+ - `analyze`
16
+ - `feedback`
17
+
18
+ ## CLI Contract (v0.2)
19
+ Both implementations must support:
20
+
21
+ ```bash
22
+ bridge read --agent <codex|gemini|claude> [--id=<substring>] [--cwd=<path>] [--chats-dir=<path>] [--json]
23
+ bridge compare --source <agent[:session-substring]>... [--cwd=<path>] [--json]
24
+ bridge report --handoff <path-to-handoff.json> [--cwd=<path>] [--json]
25
+ ```
26
+
27
+ Rules:
28
+ 1. `--cwd` defaults to current working directory when not provided.
29
+ 2. If `--id` is provided, select the most recently modified session file whose path contains the substring.
30
+ 3. If `--id` is not provided, select newest session scoped by cwd when possible.
31
+ 4. If cwd-scoped session is missing for Codex/Claude, warn and fall back to latest global session.
32
+ 5. Hard failures must exit non-zero and print a concise error.
33
+
34
+ ## JSON Output Contract (`bridge read --json`)
35
+
36
+ ```json
37
+ {
38
+ "agent": "codex",
39
+ "source": "/absolute/path/to/session-file",
40
+ "content": "last assistant/model turn or fallback text",
41
+ "warnings": [
42
+ "Warning: no Codex session matched cwd /path; falling back to latest session."
43
+ ]
44
+ }
45
+ ```
46
+
47
+ Schema is defined in `schemas/read-output.schema.json`.
48
+
49
+ `bridge report --json` outputs the coordinator report object defined by `schemas/report.schema.json`.
50
+ `bridge report --handoff` consumes packets defined by `schemas/handoff.schema.json`.
51
+
52
+ ## Redaction Rules
53
+ Implementations must redact likely secrets from returned content before printing:
54
+ - `sk-...` style API keys
55
+ - `AKIA...` style AWS access key IDs
56
+ - `Bearer <token>` headers
57
+ - `api_key|token|secret|password` key-value pairs
58
+
59
+ ## Environment Overrides (for testing and controlled installs)
60
+ - `BRIDGE_CODEX_SESSIONS_DIR`
61
+ - `BRIDGE_GEMINI_TMP_DIR`
62
+ - `BRIDGE_CLAUDE_PROJECTS_DIR`
63
+
64
+ ## Conformance
65
+ Any release must pass `scripts/conformance.sh`, which runs both implementations against shared fixtures and verifies equivalent JSON output.
package/README.md ADDED
@@ -0,0 +1,183 @@
1
+ # Inter-Agent Bridge
2
+
3
+ ![CI Status](https://github.com/cote-star/agent-bridge/actions/workflows/ci.yml/badge.svg)
4
+ ![License](https://img.shields.io/badge/license-MIT-blue.svg)
5
+ ![Version](https://img.shields.io/badge/version-0.2.0-green.svg)
6
+
7
+ **Inter-Agent Bridge** is a lightweight local protocol and reference implementation for reading cross-agent session context. It enables AI agents (Codex, Gemini, Claude) to read each other's recent session outputs from local storage, facilitating coordination, verification, and steering without a centralized cloud service.
8
+
9
+ ## 🌟 Key Tenets
10
+
11
+ 1. **Local-First**: Reads directly from local session logs (`~/.codex/sessions`, etc.) by default and does not call external services for `read`, `compare`, or `report`.
12
+ 2. **Evidence-Based**: Every claim or summary must track back to a specific source session file.
13
+ 3. **Privacy-Focused**: Automatically redacts sensitive keys (API keys, AWS tokens) before output.
14
+ 4. **Dual Parity**: Ships with both **Node.js** and **Rust** CLIs that are conformance-tested against the same output contract.
15
+
16
+ ## 🎥 Demo
17
+
18
+ ![Inter-Agent Bridge Demo](docs/demo.webp)
19
+
20
+ ## 🏗️ Architecture
21
+
22
+ The bridge acts as a universal translator for agent session formats.
23
+
24
+ ```mermaid
25
+ sequenceDiagram
26
+ participant User
27
+ participant BridgeCLI
28
+ participant Codex as ~/.codex/sessions
29
+ participant Gemini as ~/.gemini/tmp
30
+ participant Claude as ~/.claude/projects
31
+
32
+ User->>BridgeCLI: bridge read --agent codex --id "fix-bug"
33
+ BridgeCLI->>Codex: Scan & Parse JSONL
34
+ Codex-->>BridgeCLI: Raw Session Data
35
+ BridgeCLI->>BridgeCLI: Redact Secrets (sk-..., AKIA...)
36
+ BridgeCLI->>BridgeCLI: Format via Schema
37
+ BridgeCLI-->>User: Structured JSON Output
38
+ ```
39
+
40
+ ## 🚀 Feature Matrix
41
+
42
+ | Feature | Codex | Gemini | Claude |
43
+ | :----------------- | :---: | :----: | :----: |
44
+ | **Read Content** | ✅ | ✅ | ✅ |
45
+ | **Auto-Discovery** | ✅ | ✅ | ✅ |
46
+ | **CWD Scoping** | ✅ | ⚠️ | ✅ |
47
+ | **Comparisons** | ✅ | ✅ | ✅ |
48
+
49
+ > ⚠️ Gemini resolves sessions by hashing the working directory path (SHA256) to locate chat files, rather than extracting CWD metadata from session content like Codex and Claude.
50
+
51
+ ## 📦 Installation
52
+
53
+ ### Consumers (After Release)
54
+
55
+ > Available after v0.2.0 is published to npm / crates.io.
56
+
57
+ **Node.js**:
58
+
59
+ ```bash
60
+ npm install -g inter-agent-bridge-cli
61
+ bridge-node read --agent=codex --json
62
+ ```
63
+
64
+ **Rust**:
65
+
66
+ ```bash
67
+ cargo install bridge-cli
68
+ bridge read --agent codex --json
69
+ ```
70
+
71
+ ### Contributors (Developers)
72
+
73
+ Clone the repository to build from source.
74
+
75
+ **Node**:
76
+
77
+ ```bash
78
+ npm install
79
+ node scripts/read_session.cjs read --agent=codex
80
+ ```
81
+
82
+ **Rust**:
83
+
84
+ ```bash
85
+ cargo run --manifest-path cli/Cargo.toml -- read --agent codex
86
+ ```
87
+
88
+ ## 📖 Usage
89
+
90
+ > **Note**: The examples below use the `bridge` command. If you installed via Node.js (`npm`), use `bridge-node` instead.
91
+
92
+ ### Reading a Session
93
+
94
+ Get the last assistant/model output from a specific agent context.
95
+
96
+ ```bash
97
+ # Read from Codex (defaults to latest session)
98
+ bridge read --agent codex
99
+
100
+ # Read from Claude, scoped to current working directory
101
+ bridge read --agent claude --cwd /path/to/project
102
+
103
+ # Get machine-readable JSON output
104
+ bridge read --agent gemini --json
105
+ ```
106
+
107
+ ### Comparing Agents (`analyze` mode)
108
+
109
+ Compare outputs from multiple agents to detect divergence.
110
+
111
+ ```bash
112
+ bridge compare --source codex --source gemini --source claude --json
113
+ ```
114
+
115
+ ### Reporting
116
+
117
+ Generate a full coordination report from a handoff packet.
118
+
119
+ ```bash
120
+ bridge report --handoff ./handoff_packet.json --json
121
+ ```
122
+
123
+ ### Protocol-Accurate Command Contract
124
+
125
+ ```bash
126
+ bridge read --agent <codex|gemini|claude> [--id=<substring>] [--cwd=<path>] [--chats-dir=<path>] [--json]
127
+ bridge compare --source <agent[:session-substring]>... [--cwd=<path>] [--json]
128
+ bridge report --handoff <handoff.json> [--cwd=<path>] [--json]
129
+ ```
130
+
131
+ - `read` returns the latest assistant/model output found in the selected session (or fallback raw lines when structured extraction fails).
132
+ - `compare` parses each `--source` as `<agent>` (current session) or `<agent>:<session-substring>`.
133
+ - `report` consumes a handoff packet and emits structured findings/recommendations.
134
+
135
+ ## ⚙️ Configuration
136
+
137
+ Override default paths using environment variables.
138
+
139
+ | Variable | Description | Default |
140
+ | :--------------------------- | :------------------------ | :------------------- |
141
+ | `BRIDGE_CODEX_SESSIONS_DIR` | Path to Codex sessions | `~/.codex/sessions` |
142
+ | `BRIDGE_GEMINI_TMP_DIR` | Path to Gemini temp chats | `~/.gemini/tmp` |
143
+ | `BRIDGE_CLAUDE_PROJECTS_DIR` | Path to Claude projects | `~/.claude/projects` |
144
+
145
+ ## 🛠️ Development
146
+
147
+ - **Protocol**: See [PROTOCOL.md](./PROTOCOL.md) for the CLI and JSON specification.
148
+ - **Skills**: See [SKILL.md](./SKILL.md) for agentic capabilities.
149
+ - **Release**: See [docs/release.md](./docs/release.md) for publishing workflows.
150
+
151
+ ### Conformance Testing
152
+
153
+ Ensure both Node and Rust implementations return identical output for the same fixtures.
154
+
155
+ ```bash
156
+ bash scripts/conformance.sh
157
+ ```
158
+
159
+ ### README Command Checks
160
+
161
+ Run fixture-backed checks for command examples documented in this README.
162
+
163
+ ```bash
164
+ bash scripts/check_readme_examples.sh
165
+ ```
166
+
167
+ ### Schema Validation
168
+
169
+ Validate that generated reports match the JSON schema.
170
+
171
+ ```bash
172
+ bash scripts/validate_schemas.sh
173
+ ```
174
+
175
+ If your environment is offline, use:
176
+
177
+ ```bash
178
+ BRIDGE_SKIP_AJV=1 bash scripts/validate_schemas.sh
179
+ ```
180
+
181
+ ---
182
+
183
+ _Maintained by the Agent Bridge Team._
@@ -0,0 +1,32 @@
1
+ {
2
+ "mode": "verify",
3
+ "task": "Validate fixture implementation parity",
4
+ "success_criteria": [
5
+ "All sources readable",
6
+ "No divergence in fixture outputs"
7
+ ],
8
+ "sources": [
9
+ {
10
+ "agent": "codex",
11
+ "session_id": "codex-fixture",
12
+ "current_session": false,
13
+ "cwd": "/workspace/demo"
14
+ },
15
+ {
16
+ "agent": "gemini",
17
+ "session_id": "gemini-fixture",
18
+ "current_session": false,
19
+ "cwd": "/workspace/demo"
20
+ },
21
+ {
22
+ "agent": "claude",
23
+ "session_id": "claude-fixture",
24
+ "current_session": false,
25
+ "cwd": "/workspace/demo"
26
+ }
27
+ ],
28
+ "constraints": [
29
+ "No cloud dependencies",
30
+ "Keep output concise"
31
+ ]
32
+ }
@@ -0,0 +1,2 @@
1
+ {"cwd":"/workspace/demo"}
2
+ {"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"Claude fixture assistant output."}]}}
@@ -0,0 +1,2 @@
1
+ {"type":"session_meta","payload":{"cwd":"/workspace/demo"}}
2
+ {"type":"response_item","payload":{"type":"message","role":"assistant","content":"Codex fixture assistant output."}}
@@ -0,0 +1,7 @@
1
+ {
2
+ "sessionId": "gemini-fixture-0001",
3
+ "messages": [
4
+ {"type": "user", "content": "hello"},
5
+ {"type": "gemini", "content": "Gemini fixture assistant output."}
6
+ ]
7
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "inter-agent-bridge-cli",
3
+ "version": "0.2.0",
4
+ "private": false,
5
+ "type": "commonjs",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/cote-star/agent-bridge.git"
10
+ },
11
+ "homepage": "https://github.com/cote-star/agent-bridge",
12
+ "bugs": {
13
+ "url": "https://github.com/cote-star/agent-bridge/issues"
14
+ },
15
+ "bin": {
16
+ "bridge-node": "scripts/read_session.cjs"
17
+ },
18
+ "files": [
19
+ "scripts/read_session.cjs",
20
+ "PROTOCOL.md",
21
+ "README.md",
22
+ "schemas",
23
+ "fixtures"
24
+ ],
25
+ "scripts": {
26
+ "conformance": "bash scripts/conformance.sh",
27
+ "check:readme": "bash scripts/check_readme_examples.sh",
28
+ "validate:schemas": "bash scripts/validate_schemas.sh",
29
+ "check": "npm run conformance && npm run check:readme && npm run validate:schemas"
30
+ }
31
+ }
@@ -0,0 +1,64 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://inter-agent-bridge.dev/schemas/handoff.schema.json",
4
+ "title": "Coordinator Handoff Packet",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["mode", "task", "success_criteria", "sources"],
8
+ "properties": {
9
+ "mode": {
10
+ "type": "string",
11
+ "enum": ["verify", "steer", "analyze", "feedback"]
12
+ },
13
+ "task": {
14
+ "type": "string",
15
+ "minLength": 1
16
+ },
17
+ "success_criteria": {
18
+ "type": "array",
19
+ "items": { "type": "string" },
20
+ "minItems": 1
21
+ },
22
+ "sources": {
23
+ "type": "array",
24
+ "minItems": 1,
25
+ "items": {
26
+ "type": "object",
27
+ "additionalProperties": false,
28
+ "required": ["agent"],
29
+ "anyOf": [
30
+ {
31
+ "required": ["session_id"],
32
+ "properties": {
33
+ "session_id": {
34
+ "type": "string",
35
+ "minLength": 1
36
+ }
37
+ }
38
+ },
39
+ {
40
+ "required": ["current_session"],
41
+ "properties": {
42
+ "current_session": {
43
+ "const": true
44
+ }
45
+ }
46
+ }
47
+ ],
48
+ "properties": {
49
+ "agent": {
50
+ "type": "string",
51
+ "enum": ["codex", "gemini", "claude"]
52
+ },
53
+ "session_id": { "type": ["string", "null"] },
54
+ "current_session": { "type": "boolean" },
55
+ "cwd": { "type": "string" }
56
+ }
57
+ }
58
+ },
59
+ "constraints": {
60
+ "type": "array",
61
+ "items": { "type": "string" }
62
+ }
63
+ }
64
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://inter-agent-bridge.dev/schemas/read-output.schema.json",
4
+ "title": "Bridge Read Output",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["agent", "source", "content", "warnings"],
8
+ "properties": {
9
+ "agent": {
10
+ "type": "string",
11
+ "enum": ["codex", "gemini", "claude"]
12
+ },
13
+ "source": {
14
+ "type": "string",
15
+ "minLength": 1
16
+ },
17
+ "content": {
18
+ "type": "string"
19
+ },
20
+ "warnings": {
21
+ "type": "array",
22
+ "items": { "type": "string" }
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,60 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://inter-agent-bridge.dev/schemas/report.schema.json",
4
+ "title": "Coordinator Report",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["mode", "task", "sources_used", "verdict", "findings", "recommended_next_actions"],
8
+ "properties": {
9
+ "mode": {
10
+ "type": "string",
11
+ "enum": ["verify", "steer", "analyze", "feedback"]
12
+ },
13
+ "task": {
14
+ "type": "string"
15
+ },
16
+ "success_criteria": {
17
+ "type": "array",
18
+ "items": { "type": "string" }
19
+ },
20
+ "sources_used": {
21
+ "type": "array",
22
+ "items": { "type": "string" }
23
+ },
24
+ "verdict": {
25
+ "type": "string"
26
+ },
27
+ "findings": {
28
+ "type": "array",
29
+ "items": {
30
+ "type": "object",
31
+ "additionalProperties": false,
32
+ "required": ["severity", "summary", "evidence", "confidence"],
33
+ "properties": {
34
+ "severity": {
35
+ "type": "string",
36
+ "enum": ["P0", "P1", "P2", "P3"]
37
+ },
38
+ "summary": { "type": "string" },
39
+ "evidence": {
40
+ "type": "array",
41
+ "items": { "type": "string" }
42
+ },
43
+ "confidence": {
44
+ "type": "number",
45
+ "minimum": 0,
46
+ "maximum": 1
47
+ }
48
+ }
49
+ }
50
+ },
51
+ "recommended_next_actions": {
52
+ "type": "array",
53
+ "items": { "type": "string" }
54
+ },
55
+ "open_questions": {
56
+ "type": "array",
57
+ "items": { "type": "string" }
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,794 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+ const crypto = require('crypto');
7
+
8
+ const rawArgs = process.argv.slice(2);
9
+ const commandNames = new Set(['read', 'compare', 'report']);
10
+ const command = commandNames.has(rawArgs[0]) ? rawArgs[0] : 'read';
11
+ const args = commandNames.has(rawArgs[0]) ? rawArgs.slice(1) : rawArgs;
12
+
13
+ const codexSessionsBase = normalizePath(process.env.BRIDGE_CODEX_SESSIONS_DIR || '~/.codex/sessions');
14
+ const claudeProjectsBase = normalizePath(process.env.BRIDGE_CLAUDE_PROJECTS_DIR || '~/.claude/projects');
15
+ const geminiTmpBase = normalizePath(process.env.BRIDGE_GEMINI_TMP_DIR || '~/.gemini/tmp');
16
+
17
+ function expandHome(filepath) {
18
+ if (!filepath) return filepath;
19
+ if (filepath === '~') return os.homedir();
20
+ if (filepath.startsWith('~/')) {
21
+ return path.join(os.homedir(), filepath.slice(2));
22
+ }
23
+ return filepath;
24
+ }
25
+
26
+ function normalizePath(filepath) {
27
+ return path.resolve(expandHome(filepath));
28
+ }
29
+
30
+ function hashPath(filepath) {
31
+ return crypto.createHash('sha256').update(normalizePath(filepath)).digest('hex');
32
+ }
33
+
34
+ function getOptionValues(inputArgs, name) {
35
+ const values = [];
36
+ for (let i = 0; i < inputArgs.length; i += 1) {
37
+ const arg = inputArgs[i];
38
+ if (arg === name && i + 1 < inputArgs.length) {
39
+ values.push(inputArgs[i + 1]);
40
+ i += 1;
41
+ continue;
42
+ }
43
+
44
+ const prefix = `${name}=`;
45
+ if (arg.startsWith(prefix)) {
46
+ values.push(arg.slice(prefix.length));
47
+ }
48
+ }
49
+ return values;
50
+ }
51
+
52
+ function getOptionValue(inputArgs, name, fallback = null) {
53
+ const values = getOptionValues(inputArgs, name);
54
+ return values.length > 0 ? values[values.length - 1] : fallback;
55
+ }
56
+
57
+ function hasFlag(inputArgs, name) {
58
+ return inputArgs.includes(name);
59
+ }
60
+
61
+ function collectMatchingFiles(dirPath, predicate, recursive = false) {
62
+ if (!dirPath || !fs.existsSync(dirPath)) return [];
63
+
64
+ const matches = [];
65
+
66
+ function search(currentDir) {
67
+ let entries = [];
68
+ try {
69
+ entries = fs.readdirSync(currentDir, { withFileTypes: true });
70
+ } catch (error) {
71
+ return;
72
+ }
73
+
74
+ for (const entry of entries) {
75
+ const fullPath = path.join(currentDir, entry.name);
76
+ if (entry.isDirectory()) {
77
+ if (recursive) search(fullPath);
78
+ continue;
79
+ }
80
+
81
+ if (!predicate(fullPath, entry.name)) continue;
82
+
83
+ try {
84
+ const stat = fs.statSync(fullPath);
85
+ matches.push({ path: fullPath, mtimeMs: stat.mtimeMs });
86
+ } catch (error) {
87
+ // Ignore entries that disappear while scanning.
88
+ }
89
+ }
90
+ }
91
+
92
+ search(dirPath);
93
+ matches.sort((a, b) => b.mtimeMs - a.mtimeMs);
94
+ return matches;
95
+ }
96
+
97
+ function readJsonlLines(filePath) {
98
+ return fs.readFileSync(filePath, 'utf-8').split('\n').filter(Boolean);
99
+ }
100
+
101
+ function findLatestByCwd(files, cwdExtractor, expectedCwd) {
102
+ for (const file of files) {
103
+ const fileCwd = cwdExtractor(file.path);
104
+ if (fileCwd && fileCwd === expectedCwd) {
105
+ return file.path;
106
+ }
107
+ }
108
+ return null;
109
+ }
110
+
111
+ function getCodexSessionCwd(filePath) {
112
+ try {
113
+ const firstLine = readJsonlLines(filePath)[0];
114
+ if (!firstLine) return null;
115
+
116
+ const json = JSON.parse(firstLine);
117
+ if (json.type === 'session_meta' && json.payload && typeof json.payload.cwd === 'string') {
118
+ return normalizePath(json.payload.cwd);
119
+ }
120
+ } catch (error) {
121
+ return null;
122
+ }
123
+ return null;
124
+ }
125
+
126
+ function getClaudeSessionCwd(filePath) {
127
+ try {
128
+ const lines = readJsonlLines(filePath);
129
+ for (const line of lines) {
130
+ try {
131
+ const json = JSON.parse(line);
132
+ if (typeof json.cwd === 'string') {
133
+ return normalizePath(json.cwd);
134
+ }
135
+ } catch (error) {
136
+ // Ignore unparseable line.
137
+ }
138
+ }
139
+ } catch (error) {
140
+ return null;
141
+ }
142
+ return null;
143
+ }
144
+
145
+ function listGeminiChatDirs() {
146
+ if (!fs.existsSync(geminiTmpBase)) return [];
147
+
148
+ let entries = [];
149
+ try {
150
+ entries = fs.readdirSync(geminiTmpBase, { withFileTypes: true });
151
+ } catch (error) {
152
+ return [];
153
+ }
154
+
155
+ const dirs = [];
156
+ for (const entry of entries) {
157
+ if (!entry.isDirectory()) continue;
158
+ const chatsDir = path.join(geminiTmpBase, entry.name, 'chats');
159
+ if (fs.existsSync(chatsDir)) {
160
+ dirs.push(chatsDir);
161
+ }
162
+ }
163
+ return dirs;
164
+ }
165
+
166
+ function resolveGeminiChatDirs(chatsDir, cwd) {
167
+ if (chatsDir) {
168
+ const expanded = normalizePath(chatsDir);
169
+ return fs.existsSync(expanded) ? [expanded] : [];
170
+ }
171
+
172
+ const ordered = [];
173
+ const seen = new Set();
174
+
175
+ function addDir(dirPath) {
176
+ if (!dirPath || seen.has(dirPath) || !fs.existsSync(dirPath)) return;
177
+ ordered.push(dirPath);
178
+ seen.add(dirPath);
179
+ }
180
+
181
+ const scopedHash = hashPath(cwd);
182
+ addDir(path.join(geminiTmpBase, scopedHash, 'chats'));
183
+
184
+ for (const dir of listGeminiChatDirs()) {
185
+ addDir(dir);
186
+ }
187
+
188
+ return ordered;
189
+ }
190
+
191
+ function resolveCodexTargetFile(id, cwd, warnings) {
192
+ if (!fs.existsSync(codexSessionsBase)) return null;
193
+
194
+ if (id) {
195
+ const files = collectMatchingFiles(
196
+ codexSessionsBase,
197
+ (fullPath, name) => name.endsWith('.jsonl') && fullPath.includes(id),
198
+ true
199
+ );
200
+ return files.length > 0 ? files[0].path : null;
201
+ }
202
+
203
+ const files = collectMatchingFiles(codexSessionsBase, (fullPath, name) => name.endsWith('.jsonl'), true);
204
+ if (files.length === 0) return null;
205
+
206
+ const scoped = findLatestByCwd(files, getCodexSessionCwd, cwd);
207
+ if (scoped) return scoped;
208
+
209
+ warnings.push(`Warning: no Codex session matched cwd ${cwd}; falling back to latest session.`);
210
+ return files[0].path;
211
+ }
212
+
213
+ function resolveClaudeTargetFile(id, cwd, warnings) {
214
+ if (!fs.existsSync(claudeProjectsBase)) return null;
215
+
216
+ if (id) {
217
+ const files = collectMatchingFiles(
218
+ claudeProjectsBase,
219
+ (fullPath, name) => name.endsWith('.jsonl') && fullPath.includes(id),
220
+ true
221
+ );
222
+ return files.length > 0 ? files[0].path : null;
223
+ }
224
+
225
+ const files = collectMatchingFiles(claudeProjectsBase, (fullPath, name) => name.endsWith('.jsonl'), true);
226
+ if (files.length === 0) return null;
227
+
228
+ const scoped = findLatestByCwd(files, getClaudeSessionCwd, cwd);
229
+ if (scoped) return scoped;
230
+
231
+ warnings.push(`Warning: no Claude session matched cwd ${cwd}; falling back to latest session.`);
232
+ return files[0].path;
233
+ }
234
+
235
+ function resolveGeminiTargetFile(id, chatsDir, cwd) {
236
+ const dirs = resolveGeminiChatDirs(chatsDir, cwd);
237
+ if (dirs.length === 0) return { targetFile: null, searchedDirs: [] };
238
+
239
+ const candidates = [];
240
+ for (const dir of dirs) {
241
+ const files = collectMatchingFiles(
242
+ dir,
243
+ (fullPath, name) => {
244
+ if (!name.endsWith('.json')) return false;
245
+ if (id) return fullPath.includes(id);
246
+ return name.startsWith('session-');
247
+ },
248
+ false
249
+ );
250
+
251
+ for (const file of files) {
252
+ candidates.push(file);
253
+ }
254
+ }
255
+
256
+ candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
257
+ return {
258
+ targetFile: candidates.length > 0 ? candidates[0].path : null,
259
+ searchedDirs: dirs,
260
+ };
261
+ }
262
+
263
+ function extractText(value) {
264
+ if (typeof value === 'string') return value;
265
+ if (!Array.isArray(value)) return '';
266
+
267
+ return value
268
+ .map(part => {
269
+ if (typeof part === 'string') return part;
270
+ if (part && typeof part.text === 'string') return part.text;
271
+ return '';
272
+ })
273
+ .join('');
274
+ }
275
+
276
+ function extractClaudeText(value) {
277
+ if (typeof value === 'string') return value;
278
+ if (!Array.isArray(value)) return '';
279
+
280
+ return value
281
+ .filter(part => part && part.type === 'text')
282
+ .map(part => part.text || '')
283
+ .join('');
284
+ }
285
+
286
+ function redactSensitiveText(input) {
287
+ let output = String(input || '');
288
+ output = output.replace(/\bsk-[A-Za-z0-9]{20,}\b/g, 'sk-[REDACTED]');
289
+ output = output.replace(/\bAKIA[0-9A-Z]{16}\b/g, 'AKIA[REDACTED]');
290
+ output = output.replace(/\bBearer\s+[A-Za-z0-9._-]{10,}\b/gi, 'Bearer [REDACTED]');
291
+ output = output.replace(
292
+ /\b(api[_-]?key|token|secret|password)\b\s*[:=]\s*["']?[^"'\s]+["']?/gi,
293
+ (_, key) => `${key}=[REDACTED]`
294
+ );
295
+ return output;
296
+ }
297
+
298
+ function readCodexSession(id, cwd) {
299
+ const warnings = [];
300
+ const targetFile = resolveCodexTargetFile(id, cwd, warnings);
301
+ if (!targetFile) {
302
+ throw new Error('No Codex session found.');
303
+ }
304
+
305
+ const lines = readJsonlLines(targetFile);
306
+ const messages = [];
307
+ let skipped = 0;
308
+
309
+ for (const line of lines) {
310
+ try {
311
+ const json = JSON.parse(line);
312
+ if (json.type === 'response_item' && json.payload && json.payload.type === 'message') {
313
+ messages.push(json.payload);
314
+ } else if (json.type === 'event_msg' && json.payload && json.payload.type === 'agent_message') {
315
+ messages.push({ role: 'assistant', content: json.payload.message });
316
+ }
317
+ } catch (error) {
318
+ skipped += 1;
319
+ }
320
+ }
321
+
322
+ if (skipped > 0) {
323
+ warnings.push(`Warning: skipped ${skipped} unparseable line(s) in ${targetFile}`);
324
+ }
325
+
326
+ let content = '';
327
+ if (messages.length > 0) {
328
+ const assistantMsgs = messages.filter(message => (message.role || '').toLowerCase() === 'assistant');
329
+ const selected = assistantMsgs.length > 0 ? assistantMsgs[assistantMsgs.length - 1] : messages[messages.length - 1];
330
+ content = extractText(selected.content) || '[No text content]';
331
+ } else {
332
+ content = `Could not extract structured messages. Showing last 20 raw lines:\n${lines.slice(-20).join('\n')}`;
333
+ }
334
+
335
+ return {
336
+ agent: 'codex',
337
+ source: targetFile,
338
+ content: redactSensitiveText(content),
339
+ warnings,
340
+ };
341
+ }
342
+
343
+ function readGeminiSession(id, chatsDir, cwd) {
344
+ const resolved = resolveGeminiTargetFile(id, chatsDir, cwd);
345
+ const targetFile = resolved.targetFile;
346
+ if (!targetFile) {
347
+ if (chatsDir) {
348
+ throw new Error(`No Gemini session found in ${normalizePath(chatsDir)}`);
349
+ }
350
+
351
+ const lines = ['No Gemini session found. Searched chats directories:'];
352
+ for (const dir of resolved.searchedDirs) {
353
+ lines.push(` - ${dir}`);
354
+ }
355
+ throw new Error(lines.join('\n'));
356
+ }
357
+
358
+ let session;
359
+ try {
360
+ session = JSON.parse(fs.readFileSync(targetFile, 'utf-8'));
361
+ } catch (error) {
362
+ throw new Error(`Failed to parse Gemini JSON: ${error.message}`);
363
+ }
364
+
365
+ let content = '';
366
+ if (Array.isArray(session.messages)) {
367
+ const selected =
368
+ [...session.messages].reverse().find(message => {
369
+ const type = (message.type || '').toLowerCase();
370
+ return type === 'gemini' || type === 'assistant' || type === 'model';
371
+ }) || session.messages[session.messages.length - 1];
372
+
373
+ if (!selected) {
374
+ throw new Error('Gemini session has no messages.');
375
+ }
376
+
377
+ content = typeof selected.content === 'string'
378
+ ? selected.content
379
+ : extractText(selected.content) || '[No text content]';
380
+ } else if (Array.isArray(session.history)) {
381
+ const selected =
382
+ [...session.history].reverse().find(turn => (turn.role || '').toLowerCase() !== 'user') ||
383
+ session.history[session.history.length - 1];
384
+
385
+ if (!selected) {
386
+ throw new Error('Gemini history is empty.');
387
+ }
388
+
389
+ if (Array.isArray(selected.parts)) {
390
+ content = selected.parts.map(part => part.text || '').join('\n');
391
+ } else if (typeof selected.parts === 'string') {
392
+ content = selected.parts;
393
+ } else {
394
+ content = '[No text content]';
395
+ }
396
+ } else {
397
+ throw new Error('Unknown Gemini session schema. Supported fields: messages, history.');
398
+ }
399
+
400
+ return {
401
+ agent: 'gemini',
402
+ source: targetFile,
403
+ content: redactSensitiveText(content),
404
+ warnings: [],
405
+ };
406
+ }
407
+
408
+ function readClaudeSession(id, cwd) {
409
+ if (!fs.existsSync(claudeProjectsBase)) {
410
+ throw new Error(`Claude projects directory not found: ${claudeProjectsBase}`);
411
+ }
412
+
413
+ const warnings = [];
414
+ const targetFile = resolveClaudeTargetFile(id, cwd, warnings);
415
+ if (!targetFile) {
416
+ throw new Error('No Claude session found.');
417
+ }
418
+
419
+ const lines = readJsonlLines(targetFile);
420
+ const messages = [];
421
+ let skipped = 0;
422
+
423
+ for (const line of lines) {
424
+ try {
425
+ const json = JSON.parse(line);
426
+ const message = json.message || json;
427
+ if (json.type === 'assistant' || message.role === 'assistant') {
428
+ const content = message.content !== undefined ? message.content : json.content;
429
+ const text = extractClaudeText(content);
430
+ if (text) {
431
+ messages.push(text);
432
+ }
433
+ }
434
+ } catch (error) {
435
+ skipped += 1;
436
+ }
437
+ }
438
+
439
+ if (skipped > 0) {
440
+ warnings.push(`Warning: skipped ${skipped} unparseable line(s) in ${targetFile}`);
441
+ }
442
+
443
+ const content = messages.length > 0
444
+ ? messages[messages.length - 1]
445
+ : `Could not extract assistant messages. Showing last 20 raw lines:\n${lines.slice(-20).join('\n')}`;
446
+
447
+ return {
448
+ agent: 'claude',
449
+ source: targetFile,
450
+ content: redactSensitiveText(content),
451
+ warnings,
452
+ };
453
+ }
454
+
455
+ function readSource(sourceSpec, defaultCwd) {
456
+ const effectiveCwd = normalizePath(sourceSpec.cwd || defaultCwd);
457
+ if (sourceSpec.agent === 'codex') {
458
+ return readCodexSession(sourceSpec.session_id || null, effectiveCwd);
459
+ }
460
+ if (sourceSpec.agent === 'gemini') {
461
+ return readGeminiSession(sourceSpec.session_id || null, sourceSpec.chats_dir || null, effectiveCwd);
462
+ }
463
+ if (sourceSpec.agent === 'claude') {
464
+ return readClaudeSession(sourceSpec.session_id || null, effectiveCwd);
465
+ }
466
+
467
+ throw new Error(`Unsupported agent: ${sourceSpec.agent}`);
468
+ }
469
+
470
+ function parseSourceArg(raw) {
471
+ const firstColon = raw.indexOf(':');
472
+ const agent = (firstColon === -1 ? raw : raw.slice(0, firstColon)).trim().toLowerCase();
473
+ const session = firstColon === -1 ? null : raw.slice(firstColon + 1).trim();
474
+
475
+ if (!['codex', 'gemini', 'claude'].includes(agent)) {
476
+ throw new Error(`Unsupported agent: ${agent}`);
477
+ }
478
+
479
+ return {
480
+ agent,
481
+ session_id: session ? session : null,
482
+ current_session: !session,
483
+ cwd: null,
484
+ chats_dir: null,
485
+ };
486
+ }
487
+
488
+ function evidenceTag(sourceSpec) {
489
+ const id = sourceSpec.session_id ? sourceSpec.session_id.slice(0, 8) : 'latest';
490
+ return `[${sourceSpec.agent}:${id}]`;
491
+ }
492
+
493
+ function computeVerdict(mode, missingCount, uniqueCount, successCount) {
494
+ if (successCount === 0) return 'INCOMPLETE';
495
+
496
+ if (mode === 'verify') {
497
+ if (missingCount === 0 && uniqueCount <= 1) return 'PASS';
498
+ return 'FAIL';
499
+ }
500
+
501
+ if (mode === 'steer') return 'STEERING_PLAN_READY';
502
+ if (mode === 'analyze') return 'ANALYSIS_COMPLETE';
503
+ if (mode === 'feedback') return 'FEEDBACK_COMPLETE';
504
+ return 'INCOMPLETE';
505
+ }
506
+
507
+ function buildReport(request, defaultCwd) {
508
+ const successful = [];
509
+ const missing = [];
510
+
511
+ for (const sourceSpec of request.sources) {
512
+ const evidence = evidenceTag(sourceSpec);
513
+ try {
514
+ const session = readSource(sourceSpec, defaultCwd);
515
+ successful.push({ sourceSpec, session, evidence });
516
+ } catch (error) {
517
+ missing.push({ sourceSpec, error: error.message || String(error), evidence });
518
+ }
519
+ }
520
+
521
+ const findings = [];
522
+
523
+ for (const item of missing) {
524
+ findings.push({
525
+ severity: 'P1',
526
+ summary: `Source unavailable: ${item.sourceSpec.agent} (${item.error})`,
527
+ evidence: [item.evidence],
528
+ confidence: 0.9,
529
+ });
530
+ }
531
+
532
+ for (const item of successful) {
533
+ for (const warning of item.session.warnings || []) {
534
+ findings.push({
535
+ severity: 'P2',
536
+ summary: `Source warning: ${warning}`,
537
+ evidence: [item.evidence],
538
+ confidence: 0.75,
539
+ });
540
+ }
541
+ }
542
+
543
+ const uniqueContents = new Set(successful.map(item => (item.session.content || '').trim()));
544
+
545
+ if (successful.length >= 2) {
546
+ if (uniqueContents.size > 1) {
547
+ findings.push({
548
+ severity: 'P1',
549
+ summary: 'Divergent agent outputs detected',
550
+ evidence: successful.map(item => item.evidence),
551
+ confidence: 0.75,
552
+ });
553
+ } else {
554
+ findings.push({
555
+ severity: 'P3',
556
+ summary: 'All available agent outputs are aligned',
557
+ evidence: successful.map(item => item.evidence),
558
+ confidence: 0.9,
559
+ });
560
+ }
561
+ } else {
562
+ findings.push({
563
+ severity: 'P2',
564
+ summary: 'Insufficient comparable sources',
565
+ evidence: successful.map(item => item.evidence),
566
+ confidence: 0.5,
567
+ });
568
+ }
569
+
570
+ const recommendedNextActions = [];
571
+ if (missing.length > 0) {
572
+ recommendedNextActions.push('Provide valid session identifiers or cwd values for unavailable sources.');
573
+ }
574
+ if (uniqueContents.size > 1) {
575
+ recommendedNextActions.push('Inspect full transcripts for diverging sources before final decisions.');
576
+ }
577
+ if (Array.isArray(request.constraints) && request.constraints.length > 0) {
578
+ recommendedNextActions.push(`Verify recommendations against constraints: ${request.constraints.join('; ')}.`);
579
+ }
580
+ if (recommendedNextActions.length === 0) {
581
+ recommendedNextActions.push('No immediate action required.');
582
+ }
583
+
584
+ const openQuestions = missing.map(item => `Missing source ${item.sourceSpec.agent}: ${item.error}`);
585
+
586
+ return {
587
+ mode: request.mode,
588
+ task: request.task,
589
+ success_criteria: request.success_criteria,
590
+ sources_used: successful.map(item => `${item.evidence} ${item.session.source}`),
591
+ verdict: computeVerdict(request.mode, missing.length, uniqueContents.size, successful.length),
592
+ findings: findings,
593
+ recommended_next_actions: recommendedNextActions,
594
+ open_questions: openQuestions,
595
+ };
596
+ }
597
+
598
+ function renderReadResult(result, asJson) {
599
+ if (asJson) {
600
+ console.log(JSON.stringify(result, null, 2));
601
+ return;
602
+ }
603
+
604
+ for (const warning of result.warnings || []) {
605
+ console.error(warning);
606
+ }
607
+
608
+ const label = result.agent.charAt(0).toUpperCase() + result.agent.slice(1);
609
+ console.log(`SOURCE: ${label} Session (${result.source})`);
610
+ console.log('---');
611
+ console.log(result.content);
612
+ }
613
+
614
+ function renderReport(result, asJson) {
615
+ if (asJson) {
616
+ console.log(JSON.stringify(result, null, 2));
617
+ return;
618
+ }
619
+
620
+ const lines = [];
621
+ lines.push('### Inter-Agent Coordinator Report');
622
+ lines.push('');
623
+ lines.push(`**Mode:** ${result.mode}`);
624
+ lines.push(`**Task:** ${result.task}`);
625
+ lines.push('**Success Criteria:**');
626
+ for (const criterion of result.success_criteria || []) {
627
+ lines.push(`- ${criterion}`);
628
+ }
629
+ lines.push('');
630
+ lines.push('**Sources Used:**');
631
+ for (const source of result.sources_used || []) {
632
+ lines.push(`- ${source}`);
633
+ }
634
+ lines.push('');
635
+ lines.push(`**Verdict:** ${result.verdict}`);
636
+ lines.push('');
637
+ lines.push('**Findings:**');
638
+ for (const finding of result.findings || []) {
639
+ lines.push(
640
+ `- **${finding.severity}:** ${finding.summary} (evidence: ${(finding.evidence || []).join(', ')}; confidence: ${Number(finding.confidence || 0).toFixed(2)})`
641
+ );
642
+ }
643
+ lines.push('');
644
+ lines.push('**Recommended Next Actions:**');
645
+ (result.recommended_next_actions || []).forEach((action, index) => {
646
+ lines.push(`${index + 1}. ${action}`);
647
+ });
648
+ if ((result.open_questions || []).length > 0) {
649
+ lines.push('');
650
+ lines.push('**Open Questions:**');
651
+ for (const question of result.open_questions) {
652
+ lines.push(`- ${question}`);
653
+ }
654
+ }
655
+
656
+ console.log(lines.join('\n'));
657
+ }
658
+
659
+ function validateMode(mode) {
660
+ const allowed = new Set(['verify', 'steer', 'analyze', 'feedback']);
661
+ if (!allowed.has(mode)) {
662
+ throw new Error(`Unsupported mode: ${mode}`);
663
+ }
664
+ }
665
+
666
+ function runRead(inputArgs) {
667
+ const agent = getOptionValue(inputArgs, '--agent', 'codex');
668
+ const id = getOptionValue(inputArgs, '--id', null);
669
+ const chatsDir = getOptionValue(inputArgs, '--chats-dir', null);
670
+ const cwd = normalizePath(getOptionValue(inputArgs, '--cwd', process.cwd()));
671
+ const asJson = hasFlag(inputArgs, '--json');
672
+
673
+ let result;
674
+ if (agent === 'codex') {
675
+ result = readCodexSession(id, cwd);
676
+ } else if (agent === 'gemini') {
677
+ result = readGeminiSession(id, chatsDir, cwd);
678
+ } else if (agent === 'claude') {
679
+ result = readClaudeSession(id, cwd);
680
+ } else {
681
+ throw new Error(`Unknown agent: ${agent}. Supported: codex, gemini, claude`);
682
+ }
683
+
684
+ renderReadResult(result, asJson);
685
+ }
686
+
687
+ function runCompare(inputArgs) {
688
+ const sourcesRaw = getOptionValues(inputArgs, '--source');
689
+ if (sourcesRaw.length === 0) {
690
+ throw new Error('compare requires at least one --source option');
691
+ }
692
+
693
+ const cwd = normalizePath(getOptionValue(inputArgs, '--cwd', process.cwd()));
694
+ const asJson = hasFlag(inputArgs, '--json');
695
+ const sourceSpecs = sourcesRaw.map(parseSourceArg);
696
+
697
+ const report = buildReport(
698
+ {
699
+ mode: 'analyze',
700
+ task: 'Compare agent outputs',
701
+ success_criteria: [
702
+ 'Identify agreements and contradictions',
703
+ 'Highlight unavailable sources',
704
+ ],
705
+ sources: sourceSpecs,
706
+ constraints: [],
707
+ },
708
+ cwd
709
+ );
710
+
711
+ renderReport(report, asJson);
712
+ }
713
+
714
+ function runReport(inputArgs) {
715
+ const handoffPath = getOptionValue(inputArgs, '--handoff', null);
716
+ if (!handoffPath) {
717
+ throw new Error('report requires --handoff=<path>');
718
+ }
719
+
720
+ const cwd = normalizePath(getOptionValue(inputArgs, '--cwd', process.cwd()));
721
+ const asJson = hasFlag(inputArgs, '--json');
722
+
723
+ let handoff;
724
+ try {
725
+ handoff = JSON.parse(fs.readFileSync(normalizePath(handoffPath), 'utf-8'));
726
+ } catch (error) {
727
+ throw new Error(`Failed to read handoff JSON: ${error.message}`);
728
+ }
729
+
730
+ const mode = String(handoff.mode || '').toLowerCase();
731
+ validateMode(mode);
732
+
733
+ if (typeof handoff.task !== 'string' || !handoff.task.trim()) {
734
+ throw new Error('Handoff is missing required string field: task');
735
+ }
736
+ if (!Array.isArray(handoff.success_criteria) || handoff.success_criteria.length === 0) {
737
+ throw new Error('Handoff is missing required array field: success_criteria');
738
+ }
739
+ if (!Array.isArray(handoff.sources) || handoff.sources.length === 0) {
740
+ throw new Error('Handoff is missing required array field: sources');
741
+ }
742
+
743
+ const sourceSpecs = handoff.sources.map(source => {
744
+ const agent = String(source.agent || '').toLowerCase();
745
+ if (!['codex', 'gemini', 'claude'].includes(agent)) {
746
+ throw new Error(`Unsupported agent: ${agent}`);
747
+ }
748
+
749
+ const sessionId = typeof source.session_id === 'string' && source.session_id.trim()
750
+ ? source.session_id.trim()
751
+ : null;
752
+ const currentSession = source.current_session === true;
753
+
754
+ if (!sessionId && !currentSession) {
755
+ throw new Error('Each source must provide session_id or set current_session=true');
756
+ }
757
+
758
+ return {
759
+ agent,
760
+ session_id: sessionId,
761
+ current_session: currentSession,
762
+ cwd: typeof source.cwd === 'string' && source.cwd.trim() ? source.cwd : null,
763
+ chats_dir: null,
764
+ };
765
+ });
766
+
767
+ const report = buildReport(
768
+ {
769
+ mode,
770
+ task: handoff.task,
771
+ success_criteria: handoff.success_criteria.map(String),
772
+ sources: sourceSpecs,
773
+ constraints: Array.isArray(handoff.constraints) ? handoff.constraints.map(String) : [],
774
+ },
775
+ cwd
776
+ );
777
+
778
+ renderReport(report, asJson);
779
+ }
780
+
781
+ try {
782
+ if (command === 'read') {
783
+ runRead(args);
784
+ } else if (command === 'compare') {
785
+ runCompare(args);
786
+ } else if (command === 'report') {
787
+ runReport(args);
788
+ } else {
789
+ throw new Error(`Unknown command: ${command}`);
790
+ }
791
+ } catch (error) {
792
+ console.error(error.message || String(error));
793
+ process.exit(1);
794
+ }