axmap-cli 1.7.0 โ 1.9.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/.claude/commands/ax-update.md +0 -3
- package/.claude/commands/axmap-view.md +9 -0
- package/README.ko.md +17 -0
- package/README.md +17 -0
- package/bin/axmap.mjs +152 -88
- package/docs/VIEW.md +19 -0
- package/governance/vote.mjs +6 -69
- package/mcp/README.md +2 -0
- package/mcp/server.mjs +22 -7
- package/package.json +2 -1
- package/skills/axmap-view/SKILL.md +8 -0
- package/src/bridge.mjs +168 -56
- package/src/bridge.mts +104 -12
- package/src/managed-session.mjs +4 -3
- package/src/managed-session.mts +4 -3
- package/src/monitor-cli.mjs +5 -3
- package/src/monitor-cli.mts +5 -3
- package/src/session-mailbox.mjs +21 -11
- package/src/session-mailbox.mts +13 -8
- package/src/session-types.mjs +3 -0
- package/src/session-types.mts +9 -1
- package/src/terminal-bridge.mjs +13 -7
- package/src/terminal-bridge.mts +12 -7
- package/src/update.mjs +3 -20
- package/src/view.mjs +129 -0
- package/src/view.mts +115 -0
- package/src/workspace.mjs +247 -0
- package/src/workspace.mts +220 -0
- package/tools/build.mjs +1 -1
- package/tools/bus.mjs +1 -1
- package/tools/run-tests.mjs +3 -1
- package/tools/run.mjs +8 -1
- package/tools/view.mjs +10 -0
- package/src/closure.mjs +0 -343
- package/tools/vendor.mjs +0 -382
package/mcp/server.mjs
CHANGED
|
@@ -46,6 +46,7 @@ import path from 'node:path'
|
|
|
46
46
|
import readline from 'node:readline'
|
|
47
47
|
import { fileURLToPath } from 'node:url'
|
|
48
48
|
import { syncConfiguredProfile } from '../src/identity.mjs'
|
|
49
|
+
import { resolveTarget } from '../src/workspace.mjs'
|
|
49
50
|
|
|
50
51
|
const HERE = path.dirname(fileURLToPath(import.meta.url))
|
|
51
52
|
|
|
@@ -77,14 +78,26 @@ let rootsTask = null
|
|
|
77
78
|
let rootsGeneration = 0
|
|
78
79
|
let rootRequestId = 0
|
|
79
80
|
const pendingRoots = new Map()
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
/**
|
|
82
|
+
* ๐ด ์์ ํด๋๊ฐ ์ฌ๋ฌ ์ ์ฅ์๋ฅผ ๋ด์ ์์
ํด๋๋ผ ๋ชจํธํ๋ฉด ์ฌ๊ธฐ์ ์ฃฝ์ง ์๋๋ค.
|
|
83
|
+
* ์๋ฒ๊ฐ ๋ ์ผ ๋๊ตฌ ํธ์ถ์ด ๊ทธ ๋ชจํธํจ๊ณผ ๊ณ ๋ฅด๋ ๋ฐฉ๋ฒ์ ์์ด์ ํธ์๊ฒ ์๋ ค ์ค ์ ์๋ค.
|
|
84
|
+
* ๋์์ ๋๊ตฌ๋ฅผ ๋ถ๋ฅผ ๋๋ง๋ค ๋ค์ ์ ํ๋ฏ๋ก, ์ฌ๋์ด `.axmap.json` ์ ์ฐ๋ฉด ๋ค์
|
|
85
|
+
* ํธ์ถ๋ถํฐ ๋ฐ๋ก ๋ฐ๋ฅธ๋ค.
|
|
86
|
+
*/
|
|
87
|
+
let REPO
|
|
88
|
+
try { REPO = resolveRepo() } catch (e) {
|
|
89
|
+
REPO = process.cwd()
|
|
90
|
+
process.stderr.write(`axMap MCP: ๋์ ์ ์ฅ์๋ฅผ ์์ง ์ ํ์ง ๋ชปํ์ต๋๋ค.\n${e.message}\n`)
|
|
86
91
|
}
|
|
87
92
|
|
|
93
|
+
/**
|
|
94
|
+
* ํด๋ โ ๋์ ์ ์ฅ์. ์๋ก ์ฐพ๊ธฐ(Git ์)๋ฅผ ๋จผ์ ํ๊ณ , Git ๋ฐ์ด๋ฉด ์์
ํด๋ ๊ท์น
|
|
95
|
+
* (`.axmap.json` ์๋ ์ค์ โ ๋ฐ๋ก ์๋ ์ ์ฅ์ ์๋ ํ์ โ ๋ชจํธํ๋ฉด ์ค๋ฅ)์ ์ด๋ค.
|
|
96
|
+
* ๊ท์น์ `src/workspace.mjs` ํ ๊ณณ์ ์๋ค โ CLIยท๋ก์ปฌ ์ธ์
์ฐ๊ฒฐ๊ณผ ๊ฐ์ ๋ต์ ๋ด์ผ ํ๋ค.
|
|
97
|
+
* ์ ์ฅ์๊ฐ ์์ผ๋ฉด null, ๋ชจํธํ๊ฑฐ๋ ํ์ ํ์ง ๋ชปํ๋ฉด ์์ธ๋ค.
|
|
98
|
+
*/
|
|
99
|
+
function gitRoot(cwd) { return resolveTarget(cwd) }
|
|
100
|
+
|
|
88
101
|
/** Explicit pin > per-call cwd > client roots > launch cwd.
|
|
89
102
|
* A global desktop selection must never redirect another CLI session. */
|
|
90
103
|
function resolveRepo(args = {}) {
|
|
@@ -111,7 +124,9 @@ function resolveRepo(args = {}) {
|
|
|
111
124
|
return top
|
|
112
125
|
}))]
|
|
113
126
|
if (dirs.length === 1) return dirs[0]
|
|
114
|
-
|
|
127
|
+
// ์์ ํด๋๋ ์ฌ๋ฌ roots ์ค ํ๋๋ฅผ ๊ณ ๋ฅด๋ ๊ทผ๊ฑฐ๋ก๋ง ์ด๋ค. ํ์ ํ์ง ๋ชปํ๋ฉด ๊ทผ๊ฑฐ๊ฐ ์๋ ๊ฒ์ด๋ค.
|
|
128
|
+
let launched = null
|
|
129
|
+
try { launched = gitRoot(process.cwd()) } catch { /* ๊ณ ๋ฅผ ๊ทผ๊ฑฐ ์์ โ ์๋ ์ค๋ฅ */ }
|
|
115
130
|
if (dirs.includes(launched)) return launched
|
|
116
131
|
throw new Error('์์
์ ์ฅ์๋ฅผ ํ๋๋ก ์ ํ ์ ์์ต๋๋ค. ๋๊ตฌ์ cwd ์ ์์
ํด๋ ์ ๋ ๊ฒฝ๋ก๋ฅผ ์ง์ ํ์ธ์.')
|
|
117
132
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"//name": "๐ด npm ์ 'axmap' ์ 2022๋
๋ถํฐ ๋จ์ด ์ฐ๊ณ ์๋ ๋ค๋ฅธ ๊พธ๋ฌ๋ฏธ๋ค(leozin_ ์ Map ๋ผ์ด๋ธ๋ฌ๋ฆฌ, 1.1.2). ๊ทธ๋์ `npx axmap` ์ ์ฐ๋ฆฌ ๊ฒ์ด ์๋๊ณ , ๋๊ตฌ์๊ฒ๋ ๊ทธ๋ ๊ฒ ์๋ดํ๋ฉด ์ ๋๋ค. 2026-08-28 ์ axmap-cli ๋ก ์ ํ๋ค โ ์ค์ฝํ(@์ฌ์ฉ์/์ด๋ฆ)๋ฅผ ์ฐ์ง ์๋ ์ด์ ๋ ์ค์ฝํ๊ฐ npm ๊ณ์ ์ด๋ฆ์ ๋ฌถ์ฌ์ ๋์ค์ ํ์ด๋ ์กฐ์ง์ผ๋ก ์ฎ๊ธธ ๋ ์ด๋ฆ์ด ํต์งธ๋ก ๋ฐ๋๊ธฐ ๋๋ฌธ์ด๋ค. ์ฌ๋์ด ์น๋ **๋ช
๋ น** ์ด๋ฆ์ ๊ทธ๋๋ก axmap ์ด๋ค (์๋ bin) โ ๊พธ๋ฌ๋ฏธ ์ด๋ฆ๊ณผ ๋ช
๋ น ์ด๋ฆ์ ๋ฌ๋ผ๋ ๋๋ค.",
|
|
3
3
|
"name": "axmap-cli",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.9.0",
|
|
5
5
|
"//private": "๐ด ์ฌ๊ธฐ ์๋ \"private\": true ๋ฅผ 2026-08-28 ์ ์ง์ ๋ค. ๊ทธ ์ค์ด ์๋ ๋์ npm publish ๋ ๊ฑฐ๋ถ๋๋ค. ๋๋๋ฆฌ๋ ค๋ฉด ๋ค์ ๋ฃ์ผ๋ฉด ๋์ง๋ง, ์ด๋ฏธ ์ฌ๋ผ๊ฐ ๋ฒ์ ์ ๊ทธ๋๋ ์ ์ฌ๋ผ์ง๋ค โ npm ์ ๊ฐ์ ๋ฒํธ๋ฅผ ๋ฎ์ด์ฐ์ง ๋ชปํ๊ณ ์ญ์ ๋ 72์๊ฐ ์์๋ง ๋๋ค. ์ฆ ์ด ์ค์ ๋์ด๋ฆฌ๋ ๊ฒ์ '์์ผ๋ก ์ ์ฌ๋ฆฐ๋ค' ๋ ๋ป์ด์ง '์ฌ๋ฆฐ ๊ฒ์ ์์ค๋ค' ๋ ๋ป์ด ์๋๋ค.",
|
|
6
6
|
"//publishConfig": "axmap-cli ๋ ์ค์ฝํ๊ฐ ์์ด์ ๊ธฐ๋ณธ์ด ์ด๋ฏธ ๊ณต๊ฐ๋ค. ์ฆ ์ง๊ธ์ ์์ด๋ ๋๋ค. ๊ทธ๋๋ ๋จ๊ฒจ ๋๋ ์ด์ : ๋์ค์ @์กฐ์ง/axmap ์ฒ๋ผ ์ค์ฝํ๋ฅผ ๋ถ์ด๋ ๋ ์ด ์ค์ด ์์ผ๋ฉด publish ๊ฐ '์ ๋ฃ ํ๋์ด ํ์ํ๋ค'๋ ์๋ฑํ ๋ง๋ก ์คํจํ๋ค.",
|
|
7
7
|
"publishConfig": {
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"tools/",
|
|
35
35
|
"skills/",
|
|
36
36
|
"governance/",
|
|
37
|
+
"docs/VIEW.md",
|
|
37
38
|
".claude/",
|
|
38
39
|
"CLAUDE.md"
|
|
39
40
|
],
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: axmap-view
|
|
3
|
+
description: Show the current Git repository's axMap sessions, active claims, pending mailbox and recent events in a terminal view.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Use the current Git repository or the user's explicitly designated repository. Run `axmap view --cwd <absolute-repository>` in an interactive terminal; `q`, Esc or Ctrl+C closes the view. If the host cannot provide an interactive terminal, run `axmap view --cwd <absolute-repository> --once` and identify it as a snapshot. Slash-menu availability depends on the host and installed skill; do not imply that a bare slash command works in every terminal.
|
|
7
|
+
|
|
8
|
+
The display reads local evidence only. It does not start an AI session, submit work, send or collect mail, or imply that an unobserved session is idle. Never invent progress percentages or token counts.
|
package/src/bridge.mjs
CHANGED
|
@@ -9,7 +9,8 @@ import { spawnSync } from 'node:child_process';
|
|
|
9
9
|
import { fileURLToPath } from 'node:url';
|
|
10
10
|
import { withRepoLock } from './repolock.mjs';
|
|
11
11
|
import { submitConsole, checkConsole, validateConsolePrompt, findConsoleTranscript, externalStatus } from './external-console.mjs';
|
|
12
|
-
import { errorCode, errorMessage } from './session-types.mjs';
|
|
12
|
+
import { errorCode, errorMessage, RPC_NOT_DELIVERED_EXIT } from './session-types.mjs';
|
|
13
|
+
import { commonKey, resolveTarget } from './workspace.mjs';
|
|
13
14
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
14
15
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
15
16
|
const bridgeHome = () => path.join(process.env.AXMAP_STATE_DIR || path.join(os.homedir(), '.axmap'), 'bridge');
|
|
@@ -45,14 +46,23 @@ function writeJSON(p, data) {
|
|
|
45
46
|
fs.unlinkSync(tmp);
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
|
-
function
|
|
49
|
+
function commonDirKey(cwd) {
|
|
49
50
|
const r = spawnSync('git', ['rev-parse', '--path-format=absolute', '--git-common-dir'], {
|
|
50
51
|
cwd, encoding: 'utf8', windowsHide: true,
|
|
51
52
|
});
|
|
52
|
-
|
|
53
|
+
return r.status === 0 ? commonKey(fs.realpathSync(r.stdout.trim())) : null;
|
|
54
|
+
}
|
|
55
|
+
function repoKey(cwd) {
|
|
56
|
+
// Git ์์ด๋ฉด ์ง๊ธ๊น์ง์ ๊ฐ์ ํ ๋ฒ์ git ํธ์ถ๋ก ๋๋ธ๋ค. ์คํจํ์ ๋๋ง ์์
ํด๋
|
|
57
|
+
// ๊ท์น(์๋ ์ค์ โ ์์ ์ ์ฅ์ ์๋ ํ์ โ ๋ชจํธํ๋ฉด ์ค๋ฅ)์ผ๋ก ๋์ ์ ์ฅ์๋ฅผ ์ ํ๋ค.
|
|
58
|
+
const direct = commonDirKey(cwd);
|
|
59
|
+
if (direct)
|
|
60
|
+
return direct;
|
|
61
|
+
const repo = resolveTarget(cwd);
|
|
62
|
+
const key = repo && commonDirKey(repo);
|
|
63
|
+
if (!key)
|
|
53
64
|
throw new Error(`Git ์์
์ ์ฅ์๋ฅผ ํ์ธํ์ง ๋ชปํ์ต๋๋ค: ${cwd}`);
|
|
54
|
-
|
|
55
|
-
return process.platform === 'win32' ? key.toLowerCase() : key;
|
|
65
|
+
return key;
|
|
56
66
|
}
|
|
57
67
|
function addressError(address) {
|
|
58
68
|
// Only a loopback address exported by a local provider hook is accepted.
|
|
@@ -98,20 +108,96 @@ function transcript(session) {
|
|
|
98
108
|
lines.pop();
|
|
99
109
|
return lines.filter(s => s.trim()).map(s => JSON.parse(s));
|
|
100
110
|
}
|
|
101
|
-
function publicSession(s) {
|
|
111
|
+
function publicSession(s, liveness) {
|
|
102
112
|
return { id: s.id, provider: s.provider, workspaces: s.workspaces, model: s.model,
|
|
103
113
|
conversationId: s.conversationId,
|
|
104
114
|
transport: s.transport || 'agentapi', state: s.state, pid: s.pid,
|
|
105
|
-
registeredAt: s.registeredAt, event: s.event,
|
|
106
|
-
|
|
115
|
+
registeredAt: s.registeredAt, event: s.event,
|
|
116
|
+
// ํธ์คํธ๊ฐ ์ด์ ์๋์ง ํ์ธํ์ง ๋ชปํ์ผ๋ฉด ์ ์ฅ๋ "๋๊ธฐ"๋ฅผ ๋๊ธฐ๋ก ๋ณด๊ณ ํ์ง ์๋๋ค.
|
|
117
|
+
fullyIdle: liveness === 'unverified' ? false : s.fullyIdle,
|
|
118
|
+
role: s.role, usage: s.usage, usageKnown: s.usageKnown, maxTokens: s.maxTokens, pendingApproval: s.pendingApproval,
|
|
119
|
+
...(liveness ? { liveness } : {}) };
|
|
120
|
+
}
|
|
121
|
+
/** ๊ด๋ฆฌํ ์ธ์
(ptyยทstdio)์ ๋ก์ปฌ ์ ์ด ํ์ดํ. ์ธ์
ํธ์คํธ๊ฐ ์ด์ ์๋ ๋์๋ง ์กด์ฌํ๋ค. */
|
|
122
|
+
export const hostPipe = (id) => process.platform === 'win32' ? `\\\\.\\pipe\\axmap-${id}` : path.join(bridgeHome(), `${id}.sock`);
|
|
123
|
+
/** Windows ์ ์ด๋ฆฐ ํ์ดํ ์ด๋ฆ ๋ชฉ๋ก. ์ฝ์ง ๋ชปํ๋ฉด ์ค๋ฅ ์์ฒด๋ฅผ ๋๋ ค์ค๋ค โ
|
|
124
|
+
* "๋ชฉ๋ก์ ์์"๊ณผ "๋ชฉ๋ก์ ๋ชป ์ฝ์"์ ๊ฐ์ ๊ฒ์ผ๋ก ๋ณด๋ฉด ์ฐ ์ธ์
์ ๋ซํ์ผ๋ก ์ ๋๋ค. */
|
|
125
|
+
function openPipes() {
|
|
126
|
+
try {
|
|
127
|
+
return new Set(fs.readdirSync('\\\\.\\pipe\\').map(n => n.toLowerCase()));
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
return e instanceof Error ? e : new Error(String(e));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* ์ ์ฅ๋ ์ํ๊ฐ ์๋๋ผ ํธ์คํธ๊ฐ ์ค์ ๋ก ์ด์ ์๋์ง. ์ฐ๊ฒฐ ์์ฒญ์ ๋ณด๋ด์ง ์๋๋ค.
|
|
135
|
+
*
|
|
136
|
+
* `bridge serve` ๊ฐ ๊ฐ์ ์ข
๋ฃ๋๋ฉด ์ข
๋ฃ ์ฒ๋ฆฌ(state: closed)๋ฅผ ์ฐ์ง ๋ชปํด ๊ธฐ๋ก์ด
|
|
137
|
+
* ์์ํ `idle` ๋ก ๋จ๋๋ค (์ค์ธก: ์ธ์
d3cde187, ํ์ดํ ์์). ํ์ดํ๋ ํธ์คํธ๊ฐ
|
|
138
|
+
* listen ํ ๋ค์๋ง ๊ธฐ๋ก์ ์ฐ๋ฏ๋ก, ํ์ดํ๊ฐ ์๋ค๋ ๊ฒ์ ํธ์คํธ๊ฐ ๋๋ฌ๋ค๋ ๋ป์ด๋ค.
|
|
139
|
+
* Windows : ํ์ดํ ์ด๋ฆ ๋ชฉ๋ก์ ์์ผ๋ฉด dead, ๋ชฉ๋ก์ ๋ชป ์ฝ์ผ๋ฉด unverified
|
|
140
|
+
* ๊ทธ ๋ฐ : ์์ผ ํ์ผ์ด ์์ผ๋ฉด dead. ์์ผ๋ฉด ๊ธฐ๋ก๋ ํธ์คํธ pid ๋ก ํ๋จํ๊ณ ,
|
|
141
|
+
* pid ๊ฐ ์์ผ๋ฉด(์ด์ ๋ฒ์ ๊ธฐ๋ก) unverified โ ๋จ์ ์์ผ ํ์ผ๋ง์ผ๋ก๋ ๋ชจ๋ฅธ๋ค.
|
|
142
|
+
*/
|
|
143
|
+
function hostLiveness(s, pipes) {
|
|
144
|
+
if (process.platform === 'win32') {
|
|
145
|
+
const listed = pipes();
|
|
146
|
+
if (listed instanceof Error)
|
|
147
|
+
return 'unverified';
|
|
148
|
+
return listed.has(`axmap-${s.id}`.toLowerCase()) ? 'alive' : 'dead';
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
fs.statSync(hostPipe(s.id));
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
return errorCode(e) === 'ENOENT' ? 'dead' : 'unverified';
|
|
155
|
+
}
|
|
156
|
+
if (!Number.isSafeInteger(s.hostPid) || s.hostPid <= 0)
|
|
157
|
+
return 'unverified';
|
|
158
|
+
try {
|
|
159
|
+
process.kill(s.hostPid, 0);
|
|
160
|
+
return 'alive';
|
|
161
|
+
}
|
|
162
|
+
catch (e) {
|
|
163
|
+
return errorCode(e) === 'ESRCH' ? 'dead' : errorCode(e) === 'EPERM' ? 'alive' : 'unverified';
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function markClosed(target, s) {
|
|
167
|
+
// ํธ์คํธ๊ฐ ๋๋ ๋ค ๊ธฐ๋ก์ ๋ฐ๊พธ๋ ์ชฝ์ ์ฌ๊ธฐ๋ฟ์ด์ง๋ง, ๋ง ์ข
๋ฃ ์ฒ๋ฆฌ ์ค์ธ ํธ์คํธ๊ฐ ๋
|
|
168
|
+
// ์ต์ ๊ธฐ๋ก(์ฌ์ฉ๋ ๋ฑ)์ ์ผ์ ์ ์์ด ๋ค์ ์ฝ๊ณ ๋ฐ๊พผ๋ค.
|
|
169
|
+
let latest = s;
|
|
170
|
+
try {
|
|
171
|
+
latest = readJSON(target);
|
|
172
|
+
}
|
|
173
|
+
catch { /* ์๋์์ ์ฝ์ ๊ธฐ๋ก์ผ๋ก ํ์๋ง ํ๋ค */ }
|
|
174
|
+
if (latest.state === 'closed')
|
|
175
|
+
return latest;
|
|
176
|
+
const closed = { ...latest, state: 'closed', fullyIdle: false,
|
|
177
|
+
closedReason: '์ธ์
ํธ์คํธ์ ๋ก์ปฌ ์ฐ๊ฒฐ ํ์ดํ๊ฐ ์์ต๋๋ค. ์ ์ ์ข
๋ฃ ์ฒ๋ฆฌ ์์ด ๋๋ ๊ฒ์ผ๋ก ๋ณด์
๋๋ค.' };
|
|
178
|
+
// ์ ์ฅ์ ์คํจํด๋ ์ด๋ฒ ์กฐํ์๋ ๋ซํ์ผ๋ก ๋ณด๊ณ ํ๋ค. ๋ค์ ์กฐํ๊ฐ ๋ค์ ์๋ํ๋ค.
|
|
179
|
+
try {
|
|
180
|
+
writeJSON(target, closed);
|
|
181
|
+
}
|
|
182
|
+
catch { /* ์์ ๊ฐ๋ค */ }
|
|
183
|
+
return closed;
|
|
107
184
|
}
|
|
108
185
|
export function sessions(cwd) {
|
|
109
186
|
const dir = path.join(bridgeHome(), 'sessions');
|
|
110
187
|
if (!fs.existsSync(dir))
|
|
111
188
|
return [];
|
|
112
189
|
const repo = repoKey(cwd);
|
|
190
|
+
let pipes;
|
|
191
|
+
const listPipes = () => (pipes ??= openPipes());
|
|
113
192
|
return fs.readdirSync(dir).filter(n => UUID.test(n.replace(/\.json$/, '')) && n.endsWith('.json'))
|
|
114
|
-
.map(n =>
|
|
193
|
+
.map(n => [path.join(dir, n), readJSON(path.join(dir, n))])
|
|
194
|
+
.filter(([, s]) => s.repos.includes(repo))
|
|
195
|
+
.map(([target, s]) => {
|
|
196
|
+
if (!['pty', 'stdio'].includes(s.transport || '') || s.state === 'closed')
|
|
197
|
+
return publicSession(s);
|
|
198
|
+
const liveness = hostLiveness(s, listPipes);
|
|
199
|
+
return liveness === 'dead' ? publicSession(markClosed(target, s), 'dead') : publicSession(s, liveness);
|
|
200
|
+
});
|
|
115
201
|
}
|
|
116
202
|
function sessionFor(id, cwd) {
|
|
117
203
|
const s = readJSON(file('sessions', id));
|
|
@@ -217,59 +303,85 @@ export function sessionLeasePath(id) {
|
|
|
217
303
|
return path.join(bridgeHome(), 'dialogues', `session-${id}.lock`);
|
|
218
304
|
}
|
|
219
305
|
export function dispatchRequest({ to, prompt, cwd, from = 'axmap', dialogueId = null }) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
return withRepoLock(path.join(dir, `${to}.lock`), () => {
|
|
306
|
+
// ๋ณด๋ด๊ธฐ ์์ ์ ์ ๋ ์ค๋ฅ(์ด์ ์์ฒญ ๋ฏธํ์ , ๋๊ธฐ ๋ฏธํ์ธ ๋ฑ)๋ ํ์คํ ์ ๋ฌ๋์ง ์์ ๊ฒ์ด๋ค.
|
|
307
|
+
// ๋ฐ์ ํธ์งํจ์ด ์ด๋ฅผ "์ ๋ฌ ๋ถ๋ช
"๊ณผ ๊ตฌ๋ณํด์ผ ์ชฝ์ง๋ฅผ ๋ค์ ๋๊ธฐ์ด๋ก ๋๋ฆด ์ ์๋ค.
|
|
308
|
+
let sending = false;
|
|
309
|
+
try {
|
|
310
|
+
if (typeof prompt !== 'string' || !prompt.trim() || Buffer.byteLength(prompt) > 32768)
|
|
311
|
+
throw new Error('์์ฒญ ๋ณธ๋ฌธ์ 1~32768๋ฐ์ดํธ์ฌ์ผ ํฉ๋๋ค.');
|
|
312
|
+
const s = sessionFor(to, cwd);
|
|
313
|
+
const lease = sessionLeasePath(to);
|
|
229
314
|
if (fs.existsSync(lease) && readJSON(lease).dialogueId !== dialogueId)
|
|
230
|
-
throw new Error('์ด ์ธ์
์ ์๋ ๋ํ๊ฐ ์ฌ์ฉ ์ค์
๋๋ค.');
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
request
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
request
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
315
|
+
throw new Error('์ด ์ธ์
์ ์๋ ๋ํ๊ฐ ์ฌ์ฉ ์ค์
๋๋ค. ํด๋น ๋ํ๊ฐ ๋๋ ๋ค ์์ฒญํ์ธ์.');
|
|
316
|
+
const dir = path.join(bridgeHome(), 'requests');
|
|
317
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
318
|
+
return withRepoLock(path.join(dir, `${to}.lock`), () => {
|
|
319
|
+
if (fs.existsSync(lease) && readJSON(lease).dialogueId !== dialogueId)
|
|
320
|
+
throw new Error('์ด ์ธ์
์ ์๋ ๋ํ๊ฐ ์ฌ์ฉ ์ค์
๋๋ค.');
|
|
321
|
+
for (const n of fs.readdirSync(dir).filter(n => n.endsWith('.json'))) {
|
|
322
|
+
const previous = readJSON(path.join(dir, n));
|
|
323
|
+
if (previous.to !== to || !['sending', 'submitted', 'unknown'].includes(previous.state))
|
|
324
|
+
continue;
|
|
325
|
+
const latest = previous.state === 'sending' ? previous : requestResult(previous.id, cwd);
|
|
326
|
+
if (['sending', 'submitted', 'unknown'].includes(latest.state))
|
|
327
|
+
throw new Error(`์ด ์ธ์
์ ์ด์ ์์ฒญ์ด ์์ง ํ์ ๋์ง ์์์ต๋๋ค: ${latest.id}`);
|
|
328
|
+
}
|
|
329
|
+
// No fallback to `agy -p` or --conversation: both would start a separate process.
|
|
330
|
+
let consolePreflight;
|
|
331
|
+
if (s.transport === 'console') {
|
|
332
|
+
validateConsolePrompt(prompt, from);
|
|
333
|
+
consolePreflight = checkConsole(s);
|
|
334
|
+
}
|
|
335
|
+
if (!['pty', 'stdio', 'console'].includes(s.transport || '')) {
|
|
336
|
+
runAPI(s, ['get-conversation-metadata', to]);
|
|
337
|
+
if (!s.fullyIdle || s.event !== 'Stop')
|
|
338
|
+
throw new Error('์๋ ์ธ์
์ ๋๊ธฐ ์ํ๋ฅผ ์์ง ํ์ธํ์ง ๋ชปํ์ต๋๋ค. ์์
์ข
๋ฃ ํ
์ ๊ธฐ๋ค๋ฆฌ์ธ์.');
|
|
339
|
+
}
|
|
340
|
+
const request = { id: randomUUID(), to, from, repo: repoKey(cwd), prompt,
|
|
341
|
+
createdAt: new Date().toISOString(), baseline: ['pty', 'stdio'].includes(s.transport || '') ? null : s.transcript ? transcript(s).length : 0, state: 'sending' };
|
|
342
|
+
const target = file('requests', request.id);
|
|
343
|
+
writeJSON(target, request);
|
|
344
|
+
sending = true;
|
|
345
|
+
try {
|
|
346
|
+
request.receipt = s.transport === 'console' ? submitConsole(s, request, consolePreflight) : ['pty', 'stdio'].includes(s.transport || '')
|
|
347
|
+
? terminalRPC(to, { action: 'submit', request })
|
|
348
|
+
: runAPI(s, ['send-message', to, `[axMap request ${request.id}]\n์์ฒญ์: ${from}\n${prompt}`]);
|
|
349
|
+
request.state = 'submitted';
|
|
350
|
+
}
|
|
351
|
+
catch (e) {
|
|
352
|
+
if (notDelivered(e)) {
|
|
353
|
+
// ๋ก์ปฌ ํ์ดํ์ ์ฐ๊ฒฐ์กฐ์ฐจ ๋ชป ํ๋ค(ENOENTยทECONNREFUSED). ํ ๋ฐ์ดํธ๋ ๋ณด๋ด์ง
|
|
354
|
+
// ์์์ผ๋ฏ๋ก ํ์คํ ์ ๋ฌ๋์ง ์์๋ค. unknown ์ผ๋ก ๋๋ฉด ์ด ์ธ์
์ ๋ค์ ์์ฒญ์ด
|
|
355
|
+
// ์ ๋ถ ๋งํ๋ค. ์๋ ์ฌ์ ์ก์ ์ฌ์ ํ ํ์ง ์๋๋ค.
|
|
356
|
+
request.state = 'failed';
|
|
357
|
+
request.error = `์ ๋ฌ๋์ง ์์์ต๋๋ค: ${errorMessage(e)}`;
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
// A timeout may occur after delivery. Never retry automatically.
|
|
361
|
+
request.state = 'unknown';
|
|
362
|
+
request.error = errorMessage(e);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
writeJSON(target, request);
|
|
366
|
+
return request;
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
catch (e) {
|
|
370
|
+
if (!sending && e instanceof Error)
|
|
371
|
+
Object.assign(e, { notDelivered: true });
|
|
372
|
+
throw e;
|
|
373
|
+
}
|
|
268
374
|
}
|
|
375
|
+
/** ์ฐ๊ฒฐ ์ ์ ์คํจํด ์๋ฌด๊ฒ๋ ๋ณด๋ด์ง ์์ ์ค๋ฅ๋ง true. ๋ฌธ๊ตฌ๊ฐ ์๋๋ผ ์ข
๋ฃ ์ฝ๋๋ ๋ณด๋ด๊ธฐ ์ ๋จ๊ณ๋ก ๊ฐ๋ฅธ๋ค. */
|
|
376
|
+
export const notDelivered = (e) => e instanceof Error && e.notDelivered === true;
|
|
269
377
|
function terminalRPC(id, message) {
|
|
270
378
|
const r = spawnSync(process.execPath, [path.join(ROOT, 'tools', 'run.mjs'), 'rpc', id], {
|
|
271
379
|
input: JSON.stringify(message), encoding: 'utf8', windowsHide: true, timeout: 15000,
|
|
272
380
|
});
|
|
381
|
+
// ์๊ฐ ์ด๊ณผ(r.error)๋ ๊ทธ ๋ฐ์ ์คํจ๋ ์ฐ๊ฒฐ ๋ค์ ๋ฌ์ ์ ์์ด ์ ๋ฌ ์ฌ๋ถ๋ฅผ ๋ชจ๋ฅธ๋ค.
|
|
382
|
+
if (!r.error && r.status === RPC_NOT_DELIVERED_EXIT) {
|
|
383
|
+
throw Object.assign(new Error(r.stderr?.trim() || '๋ก์ปฌ ์ธ์
ํ์ดํ์ ์ฐ๊ฒฐํ์ง ๋ชปํ์ต๋๋ค.'), { notDelivered: true });
|
|
384
|
+
}
|
|
273
385
|
if (r.error || r.status !== 0)
|
|
274
386
|
throw new Error(r.error?.message || r.stderr?.trim() || 'ํฐ๋ฏธ๋ ์ธ์
์ ์ฐ๊ฒฐํ์ง ๋ชปํ์ต๋๋ค.');
|
|
275
387
|
return JSON.parse(r.stdout);
|
package/src/bridge.mts
CHANGED
|
@@ -8,7 +8,9 @@ import { spawnSync } from 'node:child_process'
|
|
|
8
8
|
import { fileURLToPath } from 'node:url'
|
|
9
9
|
import { withRepoLock } from './repolock.mjs'
|
|
10
10
|
import { submitConsole, checkConsole, validateConsolePrompt, findConsoleTranscript, externalStatus } from './external-console.mjs'
|
|
11
|
-
import { errorCode, errorMessage } from './session-types.mjs'
|
|
11
|
+
import { errorCode, errorMessage, RPC_NOT_DELIVERED_EXIT } from './session-types.mjs'
|
|
12
|
+
import { commonKey, resolveTarget } from './workspace.mjs'
|
|
13
|
+
import type { HostLiveness } from './session-types.mjs'
|
|
12
14
|
import type { SessionRecord, PublicSession, RequestRecord, DispatchOptions, HookInput, TranscriptEntry } from './session-types.mjs'
|
|
13
15
|
|
|
14
16
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
@@ -37,13 +39,21 @@ function writeJSON(p: string, data: unknown): void {
|
|
|
37
39
|
}
|
|
38
40
|
} finally { if (fs.existsSync(tmp)) fs.unlinkSync(tmp) }
|
|
39
41
|
}
|
|
40
|
-
function
|
|
42
|
+
function commonDirKey(cwd: string): string | null {
|
|
41
43
|
const r = spawnSync('git', ['rev-parse', '--path-format=absolute', '--git-common-dir'], {
|
|
42
44
|
cwd, encoding: 'utf8', windowsHide: true,
|
|
43
45
|
})
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
return r.status === 0 ? commonKey(fs.realpathSync(r.stdout.trim())) : null
|
|
47
|
+
}
|
|
48
|
+
function repoKey(cwd: string): string {
|
|
49
|
+
// Git ์์ด๋ฉด ์ง๊ธ๊น์ง์ ๊ฐ์ ํ ๋ฒ์ git ํธ์ถ๋ก ๋๋ธ๋ค. ์คํจํ์ ๋๋ง ์์
ํด๋
|
|
50
|
+
// ๊ท์น(์๋ ์ค์ โ ์์ ์ ์ฅ์ ์๋ ํ์ โ ๋ชจํธํ๋ฉด ์ค๋ฅ)์ผ๋ก ๋์ ์ ์ฅ์๋ฅผ ์ ํ๋ค.
|
|
51
|
+
const direct = commonDirKey(cwd)
|
|
52
|
+
if (direct) return direct
|
|
53
|
+
const repo = resolveTarget(cwd)
|
|
54
|
+
const key = repo && commonDirKey(repo)
|
|
55
|
+
if (!key) throw new Error(`Git ์์
์ ์ฅ์๋ฅผ ํ์ธํ์ง ๋ชปํ์ต๋๋ค: ${cwd}`)
|
|
56
|
+
return key
|
|
47
57
|
}
|
|
48
58
|
function addressError(address: unknown): asserts address is string {
|
|
49
59
|
// Only a loopback address exported by a local provider hook is accepted.
|
|
@@ -79,19 +89,77 @@ function transcript(session: SessionRecord): TranscriptEntry[] {
|
|
|
79
89
|
lines.pop()
|
|
80
90
|
return lines.filter(s => s.trim()).map(s => JSON.parse(s) as TranscriptEntry)
|
|
81
91
|
}
|
|
82
|
-
function publicSession(s: SessionRecord): PublicSession {
|
|
92
|
+
function publicSession(s: SessionRecord, liveness?: HostLiveness): PublicSession {
|
|
83
93
|
return { id: s.id, provider: s.provider, workspaces: s.workspaces, model: s.model,
|
|
84
94
|
conversationId: s.conversationId,
|
|
85
95
|
transport: s.transport || 'agentapi', state: s.state, pid: s.pid,
|
|
86
|
-
registeredAt: s.registeredAt, event: s.event,
|
|
87
|
-
|
|
96
|
+
registeredAt: s.registeredAt, event: s.event,
|
|
97
|
+
// ํธ์คํธ๊ฐ ์ด์ ์๋์ง ํ์ธํ์ง ๋ชปํ์ผ๋ฉด ์ ์ฅ๋ "๋๊ธฐ"๋ฅผ ๋๊ธฐ๋ก ๋ณด๊ณ ํ์ง ์๋๋ค.
|
|
98
|
+
fullyIdle: liveness === 'unverified' ? false : s.fullyIdle,
|
|
99
|
+
role: s.role, usage: s.usage, usageKnown: s.usageKnown, maxTokens: s.maxTokens, pendingApproval: s.pendingApproval,
|
|
100
|
+
...(liveness ? { liveness } : {}) }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** ๊ด๋ฆฌํ ์ธ์
(ptyยทstdio)์ ๋ก์ปฌ ์ ์ด ํ์ดํ. ์ธ์
ํธ์คํธ๊ฐ ์ด์ ์๋ ๋์๋ง ์กด์ฌํ๋ค. */
|
|
104
|
+
export const hostPipe = (id: string) => process.platform === 'win32' ? `\\\\.\\pipe\\axmap-${id}` : path.join(bridgeHome(), `${id}.sock`)
|
|
105
|
+
|
|
106
|
+
/** Windows ์ ์ด๋ฆฐ ํ์ดํ ์ด๋ฆ ๋ชฉ๋ก. ์ฝ์ง ๋ชปํ๋ฉด ์ค๋ฅ ์์ฒด๋ฅผ ๋๋ ค์ค๋ค โ
|
|
107
|
+
* "๋ชฉ๋ก์ ์์"๊ณผ "๋ชฉ๋ก์ ๋ชป ์ฝ์"์ ๊ฐ์ ๊ฒ์ผ๋ก ๋ณด๋ฉด ์ฐ ์ธ์
์ ๋ซํ์ผ๋ก ์ ๋๋ค. */
|
|
108
|
+
function openPipes(): Set<string> | Error {
|
|
109
|
+
try { return new Set(fs.readdirSync('\\\\.\\pipe\\').map(n => n.toLowerCase())) }
|
|
110
|
+
catch (e) { return e instanceof Error ? e : new Error(String(e)) }
|
|
88
111
|
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* ์ ์ฅ๋ ์ํ๊ฐ ์๋๋ผ ํธ์คํธ๊ฐ ์ค์ ๋ก ์ด์ ์๋์ง. ์ฐ๊ฒฐ ์์ฒญ์ ๋ณด๋ด์ง ์๋๋ค.
|
|
115
|
+
*
|
|
116
|
+
* `bridge serve` ๊ฐ ๊ฐ์ ์ข
๋ฃ๋๋ฉด ์ข
๋ฃ ์ฒ๋ฆฌ(state: closed)๋ฅผ ์ฐ์ง ๋ชปํด ๊ธฐ๋ก์ด
|
|
117
|
+
* ์์ํ `idle` ๋ก ๋จ๋๋ค (์ค์ธก: ์ธ์
d3cde187, ํ์ดํ ์์). ํ์ดํ๋ ํธ์คํธ๊ฐ
|
|
118
|
+
* listen ํ ๋ค์๋ง ๊ธฐ๋ก์ ์ฐ๋ฏ๋ก, ํ์ดํ๊ฐ ์๋ค๋ ๊ฒ์ ํธ์คํธ๊ฐ ๋๋ฌ๋ค๋ ๋ป์ด๋ค.
|
|
119
|
+
* Windows : ํ์ดํ ์ด๋ฆ ๋ชฉ๋ก์ ์์ผ๋ฉด dead, ๋ชฉ๋ก์ ๋ชป ์ฝ์ผ๋ฉด unverified
|
|
120
|
+
* ๊ทธ ๋ฐ : ์์ผ ํ์ผ์ด ์์ผ๋ฉด dead. ์์ผ๋ฉด ๊ธฐ๋ก๋ ํธ์คํธ pid ๋ก ํ๋จํ๊ณ ,
|
|
121
|
+
* pid ๊ฐ ์์ผ๋ฉด(์ด์ ๋ฒ์ ๊ธฐ๋ก) unverified โ ๋จ์ ์์ผ ํ์ผ๋ง์ผ๋ก๋ ๋ชจ๋ฅธ๋ค.
|
|
122
|
+
*/
|
|
123
|
+
function hostLiveness(s: SessionRecord, pipes: () => Set<string> | Error): HostLiveness {
|
|
124
|
+
if (process.platform === 'win32') {
|
|
125
|
+
const listed = pipes()
|
|
126
|
+
if (listed instanceof Error) return 'unverified'
|
|
127
|
+
return listed.has(`axmap-${s.id}`.toLowerCase()) ? 'alive' : 'dead'
|
|
128
|
+
}
|
|
129
|
+
try { fs.statSync(hostPipe(s.id)) } catch (e) { return errorCode(e) === 'ENOENT' ? 'dead' : 'unverified' }
|
|
130
|
+
if (!Number.isSafeInteger(s.hostPid) || (s.hostPid as number) <= 0) return 'unverified'
|
|
131
|
+
try { process.kill(s.hostPid as number, 0); return 'alive' } catch (e) {
|
|
132
|
+
return errorCode(e) === 'ESRCH' ? 'dead' : errorCode(e) === 'EPERM' ? 'alive' : 'unverified'
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function markClosed(target: string, s: SessionRecord): SessionRecord {
|
|
137
|
+
// ํธ์คํธ๊ฐ ๋๋ ๋ค ๊ธฐ๋ก์ ๋ฐ๊พธ๋ ์ชฝ์ ์ฌ๊ธฐ๋ฟ์ด์ง๋ง, ๋ง ์ข
๋ฃ ์ฒ๋ฆฌ ์ค์ธ ํธ์คํธ๊ฐ ๋
|
|
138
|
+
// ์ต์ ๊ธฐ๋ก(์ฌ์ฉ๋ ๋ฑ)์ ์ผ์ ์ ์์ด ๋ค์ ์ฝ๊ณ ๋ฐ๊พผ๋ค.
|
|
139
|
+
let latest = s
|
|
140
|
+
try { latest = readJSON<SessionRecord>(target) } catch { /* ์๋์์ ์ฝ์ ๊ธฐ๋ก์ผ๋ก ํ์๋ง ํ๋ค */ }
|
|
141
|
+
if (latest.state === 'closed') return latest
|
|
142
|
+
const closed: SessionRecord = { ...latest, state: 'closed', fullyIdle: false,
|
|
143
|
+
closedReason: '์ธ์
ํธ์คํธ์ ๋ก์ปฌ ์ฐ๊ฒฐ ํ์ดํ๊ฐ ์์ต๋๋ค. ์ ์ ์ข
๋ฃ ์ฒ๋ฆฌ ์์ด ๋๋ ๊ฒ์ผ๋ก ๋ณด์
๋๋ค.' }
|
|
144
|
+
// ์ ์ฅ์ ์คํจํด๋ ์ด๋ฒ ์กฐํ์๋ ๋ซํ์ผ๋ก ๋ณด๊ณ ํ๋ค. ๋ค์ ์กฐํ๊ฐ ๋ค์ ์๋ํ๋ค.
|
|
145
|
+
try { writeJSON(target, closed) } catch { /* ์์ ๊ฐ๋ค */ }
|
|
146
|
+
return closed
|
|
147
|
+
}
|
|
148
|
+
|
|
89
149
|
export function sessions(cwd: string): PublicSession[] {
|
|
90
150
|
const dir = path.join(bridgeHome(), 'sessions')
|
|
91
151
|
if (!fs.existsSync(dir)) return []
|
|
92
152
|
const repo = repoKey(cwd)
|
|
153
|
+
let pipes: Set<string> | Error | undefined
|
|
154
|
+
const listPipes = () => (pipes ??= openPipes())
|
|
93
155
|
return fs.readdirSync(dir).filter(n => UUID.test(n.replace(/\.json$/, '')) && n.endsWith('.json'))
|
|
94
|
-
.map(n => readJSON<SessionRecord>(path.join(dir, n))
|
|
156
|
+
.map(n => [path.join(dir, n), readJSON<SessionRecord>(path.join(dir, n))] as const)
|
|
157
|
+
.filter(([, s]) => s.repos.includes(repo))
|
|
158
|
+
.map(([target, s]) => {
|
|
159
|
+
if (!['pty', 'stdio'].includes(s.transport || '') || s.state === 'closed') return publicSession(s)
|
|
160
|
+
const liveness = hostLiveness(s, listPipes)
|
|
161
|
+
return liveness === 'dead' ? publicSession(markClosed(target, s), 'dead') : publicSession(s, liveness)
|
|
162
|
+
})
|
|
95
163
|
}
|
|
96
164
|
function sessionFor(id: string, cwd: string): SessionRecord {
|
|
97
165
|
const s = readJSON<SessionRecord>(file('sessions', id))
|
|
@@ -180,6 +248,10 @@ export function sessionLeasePath(id: string): string {
|
|
|
180
248
|
}
|
|
181
249
|
|
|
182
250
|
export function dispatchRequest({ to, prompt, cwd, from = 'axmap', dialogueId = null }: DispatchOptions): RequestRecord {
|
|
251
|
+
// ๋ณด๋ด๊ธฐ ์์ ์ ์ ๋ ์ค๋ฅ(์ด์ ์์ฒญ ๋ฏธํ์ , ๋๊ธฐ ๋ฏธํ์ธ ๋ฑ)๋ ํ์คํ ์ ๋ฌ๋์ง ์์ ๊ฒ์ด๋ค.
|
|
252
|
+
// ๋ฐ์ ํธ์งํจ์ด ์ด๋ฅผ "์ ๋ฌ ๋ถ๋ช
"๊ณผ ๊ตฌ๋ณํด์ผ ์ชฝ์ง๋ฅผ ๋ค์ ๋๊ธฐ์ด๋ก ๋๋ฆด ์ ์๋ค.
|
|
253
|
+
let sending = false
|
|
254
|
+
try {
|
|
183
255
|
if (typeof prompt !== 'string' || !prompt.trim() || Buffer.byteLength(prompt) > 32768) throw new Error('์์ฒญ ๋ณธ๋ฌธ์ 1~32768๋ฐ์ดํธ์ฌ์ผ ํฉ๋๋ค.')
|
|
184
256
|
const s = sessionFor(to, cwd)
|
|
185
257
|
const lease = sessionLeasePath(to)
|
|
@@ -205,25 +277,45 @@ export function dispatchRequest({ to, prompt, cwd, from = 'axmap', dialogueId =
|
|
|
205
277
|
createdAt: new Date().toISOString(), baseline: ['pty', 'stdio'].includes(s.transport || '') ? null : s.transcript ? transcript(s).length : 0, state: 'sending' }
|
|
206
278
|
const target = file('requests', request.id)
|
|
207
279
|
writeJSON(target, request)
|
|
280
|
+
sending = true
|
|
208
281
|
try {
|
|
209
282
|
request.receipt = s.transport === 'console' ? submitConsole(s, request, consolePreflight) : ['pty', 'stdio'].includes(s.transport || '')
|
|
210
283
|
? terminalRPC(to, { action: 'submit', request })
|
|
211
284
|
: runAPI(s, ['send-message', to, `[axMap request ${request.id}]\n์์ฒญ์: ${from}\n${prompt}`])
|
|
212
285
|
request.state = 'submitted'
|
|
213
286
|
} catch (e) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
287
|
+
if (notDelivered(e)) {
|
|
288
|
+
// ๋ก์ปฌ ํ์ดํ์ ์ฐ๊ฒฐ์กฐ์ฐจ ๋ชป ํ๋ค(ENOENTยทECONNREFUSED). ํ ๋ฐ์ดํธ๋ ๋ณด๋ด์ง
|
|
289
|
+
// ์์์ผ๋ฏ๋ก ํ์คํ ์ ๋ฌ๋์ง ์์๋ค. unknown ์ผ๋ก ๋๋ฉด ์ด ์ธ์
์ ๋ค์ ์์ฒญ์ด
|
|
290
|
+
// ์ ๋ถ ๋งํ๋ค. ์๋ ์ฌ์ ์ก์ ์ฌ์ ํ ํ์ง ์๋๋ค.
|
|
291
|
+
request.state = 'failed'
|
|
292
|
+
request.error = `์ ๋ฌ๋์ง ์์์ต๋๋ค: ${errorMessage(e)}`
|
|
293
|
+
} else {
|
|
294
|
+
// A timeout may occur after delivery. Never retry automatically.
|
|
295
|
+
request.state = 'unknown'
|
|
296
|
+
request.error = errorMessage(e)
|
|
297
|
+
}
|
|
217
298
|
}
|
|
218
299
|
writeJSON(target, request)
|
|
219
300
|
return request
|
|
220
301
|
})
|
|
302
|
+
} catch (e) {
|
|
303
|
+
if (!sending && e instanceof Error) Object.assign(e, { notDelivered: true })
|
|
304
|
+
throw e
|
|
305
|
+
}
|
|
221
306
|
}
|
|
222
307
|
|
|
308
|
+
/** ์ฐ๊ฒฐ ์ ์ ์คํจํด ์๋ฌด๊ฒ๋ ๋ณด๋ด์ง ์์ ์ค๋ฅ๋ง true. ๋ฌธ๊ตฌ๊ฐ ์๋๋ผ ์ข
๋ฃ ์ฝ๋๋ ๋ณด๋ด๊ธฐ ์ ๋จ๊ณ๋ก ๊ฐ๋ฅธ๋ค. */
|
|
309
|
+
export const notDelivered =(e: unknown) => e instanceof Error && (e as Error & { notDelivered?: boolean }).notDelivered === true
|
|
310
|
+
|
|
223
311
|
function terminalRPC(id: string, message: unknown): unknown {
|
|
224
312
|
const r = spawnSync(process.execPath, [path.join(ROOT, 'tools', 'run.mjs'), 'rpc', id], {
|
|
225
313
|
input: JSON.stringify(message), encoding: 'utf8', windowsHide: true, timeout: 15000,
|
|
226
314
|
})
|
|
315
|
+
// ์๊ฐ ์ด๊ณผ(r.error)๋ ๊ทธ ๋ฐ์ ์คํจ๋ ์ฐ๊ฒฐ ๋ค์ ๋ฌ์ ์ ์์ด ์ ๋ฌ ์ฌ๋ถ๋ฅผ ๋ชจ๋ฅธ๋ค.
|
|
316
|
+
if (!r.error && r.status === RPC_NOT_DELIVERED_EXIT) {
|
|
317
|
+
throw Object.assign(new Error(r.stderr?.trim() || '๋ก์ปฌ ์ธ์
ํ์ดํ์ ์ฐ๊ฒฐํ์ง ๋ชปํ์ต๋๋ค.'), { notDelivered: true })
|
|
318
|
+
}
|
|
227
319
|
if (r.error || r.status !== 0) throw new Error(r.error?.message || r.stderr?.trim() || 'ํฐ๋ฏธ๋ ์ธ์
์ ์ฐ๊ฒฐํ์ง ๋ชปํ์ต๋๋ค.')
|
|
228
320
|
return JSON.parse(r.stdout)
|
|
229
321
|
}
|
package/src/managed-session.mjs
CHANGED
|
@@ -6,11 +6,12 @@ import path from 'node:path';
|
|
|
6
6
|
import net from 'node:net';
|
|
7
7
|
import { spawn } from 'node:child_process';
|
|
8
8
|
import { randomUUID, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
9
|
-
import { bridgeHome, repoKey, writeJSON } from './bridge.mjs';
|
|
9
|
+
import { bridgeHome, hostPipe, repoKey, writeJSON } from './bridge.mjs';
|
|
10
10
|
import { normalizeUsage, usageDelta } from './session-usage.mjs';
|
|
11
11
|
import { errorMessage } from './session-types.mjs';
|
|
12
12
|
import { connectCodexProxy } from './codex-connection.mjs';
|
|
13
|
-
|
|
13
|
+
// ์์กด ํ์ธ(bridge.sessions)์ด ๊ฐ์ ์ด๋ฆ์ ๋ณด๋๋ก ํ ๊ณณ์์ ๋ง๋ ๋ค.
|
|
14
|
+
const pipeFor = hostPipe;
|
|
14
15
|
const uuid = (value) => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value || '');
|
|
15
16
|
export async function startManagedSession({ provider, executable = provider, prefix = [], args = null, model, role = '', existingThreadId, socketPath, observeActive = false, cwd = process.cwd(), maxTokens = 100000, timeoutMs = 120000, onEvent = () => { } }) {
|
|
16
17
|
if (!['codex', 'agy'].includes(provider))
|
|
@@ -24,7 +25,7 @@ export async function startManagedSession({ provider, executable = provider, pre
|
|
|
24
25
|
const id = randomUUID(), token = randomBytes(32).toString('hex');
|
|
25
26
|
const sessionPath = path.join(bridgeHome(), 'sessions', `${id}.json`);
|
|
26
27
|
const s = { id, token, provider, transport: 'stdio', role, model: model || null,
|
|
27
|
-
workspaces: [path.resolve(cwd)], repos: [repoKey(cwd)], state: 'starting', fullyIdle: false,
|
|
28
|
+
workspaces: [path.resolve(cwd)], repos: [repoKey(cwd)], state: 'starting', fullyIdle: false, hostPid: process.pid,
|
|
28
29
|
registeredAt: new Date().toISOString(), usage: null, usageKnown: true, maxTokens };
|
|
29
30
|
const jobs = new Map();
|
|
30
31
|
let attachedUsageOrigin = null;
|
package/src/managed-session.mts
CHANGED
|
@@ -5,7 +5,7 @@ import path from 'node:path'
|
|
|
5
5
|
import net from 'node:net'
|
|
6
6
|
import { spawn } from 'node:child_process'
|
|
7
7
|
import { randomUUID, randomBytes, timingSafeEqual } from 'node:crypto'
|
|
8
|
-
import { bridgeHome, repoKey, writeJSON } from './bridge.mjs'
|
|
8
|
+
import { bridgeHome, hostPipe, repoKey, writeJSON } from './bridge.mjs'
|
|
9
9
|
import { normalizeUsage, usageDelta } from './session-usage.mjs'
|
|
10
10
|
import { errorMessage } from './session-types.mjs'
|
|
11
11
|
import { connectCodexProxy } from './codex-connection.mjs'
|
|
@@ -31,7 +31,8 @@ interface ProviderMessage {
|
|
|
31
31
|
}
|
|
32
32
|
interface AgyResult { conversation_id: string; status: string; response?: unknown; error?: string; usage?: unknown }
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
// ์์กด ํ์ธ(bridge.sessions)์ด ๊ฐ์ ์ด๋ฆ์ ๋ณด๋๋ก ํ ๊ณณ์์ ๋ง๋ ๋ค.
|
|
35
|
+
const pipeFor = hostPipe
|
|
35
36
|
const uuid = (value: string | undefined) => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value || '')
|
|
36
37
|
|
|
37
38
|
export async function startManagedSession({ provider, executable = provider, prefix = [], args = null, model, role = '', existingThreadId, socketPath, observeActive = false,
|
|
@@ -43,7 +44,7 @@ export async function startManagedSession({ provider, executable = provider, pre
|
|
|
43
44
|
const id = randomUUID(), token = randomBytes(32).toString('hex')
|
|
44
45
|
const sessionPath = path.join(bridgeHome(), 'sessions', `${id}.json`)
|
|
45
46
|
const s: SessionRecord = { id, token, provider, transport: 'stdio', role, model: model || null,
|
|
46
|
-
workspaces: [path.resolve(cwd)], repos: [repoKey(cwd)], state: 'starting', fullyIdle: false,
|
|
47
|
+
workspaces: [path.resolve(cwd)], repos: [repoKey(cwd)], state: 'starting', fullyIdle: false, hostPid: process.pid,
|
|
47
48
|
registeredAt: new Date().toISOString(), usage: null, usageKnown: true, maxTokens }
|
|
48
49
|
const jobs = new Map<string, JobRecord>()
|
|
49
50
|
let attachedUsageOrigin: Usage | null = null
|
package/src/monitor-cli.mjs
CHANGED
|
@@ -3,12 +3,13 @@ import path from 'node:path';
|
|
|
3
3
|
import { createMonitor, renderMonitor } from './monitor.mjs';
|
|
4
4
|
import { errorMessage } from './session-types.mjs';
|
|
5
5
|
import { renderPipelineGraph } from './pipeline-graph.mjs';
|
|
6
|
+
import { createView, renderView } from './view.mjs';
|
|
6
7
|
export async function runMonitor(args) {
|
|
7
8
|
if (args.includes('--help') || args.includes('-h')) {
|
|
8
9
|
console.log('axmap monitor [--cwd <repo>] [--interval 1000] [--once | --json] [--no-color]\n์ธ์
ยท์์ฒญ ํ์ดํ๋ผ์ธยท์๋ ๋ํยท์ ์ ยท์ต๊ทผ ์ด๋ฒคํธ๋ฅผ ์ค์๊ฐ์ผ๋ก ํ์ํฉ๋๋ค.\nTTY์์๋ ๊ณ์ ๊ฐฑ์ ํ๋ฉฐ, ํ์ดํ/๋ฆฌ๋๋ ์
์์๋ ํ ๋ฒ๋ง ์ถ๋ ฅํฉ๋๋ค. q / Esc / Ctrl+C ์ข
๋ฃ.');
|
|
9
10
|
return;
|
|
10
11
|
}
|
|
11
|
-
const allowed = new Set(['--cwd', '--interval', '--once', '--json', '--no-color', '--graph']);
|
|
12
|
+
const allowed = new Set(['--cwd', '--interval', '--once', '--json', '--no-color', '--graph', '--view']);
|
|
12
13
|
for (let i = 0; i < args.length; i++) {
|
|
13
14
|
if (!allowed.has(args[i]))
|
|
14
15
|
throw new Error(`์ ์ ์๋ ์ต์
: ${args[i]}`);
|
|
@@ -22,8 +23,9 @@ export async function runMonitor(args) {
|
|
|
22
23
|
const interval = Number(option('--interval') || 1000);
|
|
23
24
|
if (!Number.isInteger(interval) || interval < 250 || interval > 60000)
|
|
24
25
|
throw new Error('interval์ 250~60000ms ์ ์์ฌ์ผ ํฉ๋๋ค.');
|
|
25
|
-
const
|
|
26
|
-
const
|
|
26
|
+
const cwd = path.resolve(option('--cwd') || process.cwd());
|
|
27
|
+
const collect = args.includes('--view') ? createView(cwd) : createMonitor(cwd);
|
|
28
|
+
const render = args.includes('--view') ? renderView : args.includes('--graph') ? renderPipelineGraph : renderMonitor;
|
|
27
29
|
const live = !!process.stdout.isTTY && !!process.stdin.isTTY && process.env.TERM !== 'dumb' && !args.includes('--once') && !args.includes('--json');
|
|
28
30
|
const color = !!process.stdout.isTTY && !args.includes('--no-color') && process.env.NO_COLOR === undefined && process.env.TERM !== 'dumb';
|
|
29
31
|
if (!live) {
|