axmap-cli 1.8.0 โ 1.10.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/README.ko.md +31 -0
- package/README.md +31 -0
- package/bin/axmap.mjs +149 -88
- package/governance/vote.mjs +6 -69
- package/mcp/README.md +2 -0
- package/mcp/server.mjs +23 -8
- package/package.json +1 -1
- package/src/bridge.mjs +313 -61
- package/src/bridge.mts +208 -22
- package/src/external-console.mjs +45 -12
- package/src/external-console.mts +44 -14
- package/src/managed-session.mjs +105 -15
- package/src/managed-session.mts +84 -18
- package/src/monitor.mjs +3 -3
- package/src/monitor.mts +3 -3
- package/src/pipeline-mcp.mjs +4 -4
- package/src/pipeline-mcp.mts +4 -4
- package/src/session-dialogue.mjs +21 -5
- package/src/session-dialogue.mts +22 -5
- 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 +18 -6
- package/src/session-usage.mjs +16 -0
- package/src/session-usage.mts +17 -1
- package/src/terminal-bridge.mjs +29 -15
- package/src/terminal-bridge.mts +28 -16
- package/src/update.mjs +3 -20
- package/src/workspace.mjs +247 -0
- package/src/workspace.mts +220 -0
- package/tools/bridge.mjs +14 -6
- package/tools/build.mjs +1 -1
- package/tools/bus.mjs +1 -1
- package/tools/console-queue.mjs +3 -0
- package/tools/run.mjs +11 -4
- package/tools/setup.mjs +1 -1
- package/tools/windows-console.cs +24 -8
- package/tools/windows-console.ps1 +14 -5
- 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
|
}
|
|
@@ -493,7 +508,7 @@ const TOOLS = [
|
|
|
493
508
|
},
|
|
494
509
|
{
|
|
495
510
|
name: 'ax_dispatch',
|
|
496
|
-
description: '๋ช
์์ ์ผ๋ก ์๋์ ์คํ ํด์ด ํ์ํ ์์
์ฉ
|
|
511
|
+
description: '๋ช
์์ ์ผ๋ก ์๋์ ์คํ ํด์ด ํ์ํ ์์
์ฉ ์ ๋ฌ์ด๋ค. ๊ด๋ฆฌํ Claude CodeยทCodexยทagy์ Windows ์ผ๋ฐ Claude Code(console)๋ ๋๊ธฐ ์ค ์คํํ๊ณ ์์
ยท์
๋ ฅยท์น์ธ ๋๊ธฐ ์ค์ด๋ฉด FIFO ํ์ ์ ์ํด idle์์ ์๋ ์คํํ๋ค. ๊ธฐ์กด ๋งฅ๋ฝ๊ณผ ํ๋ก์ธ์ค๋ฅผ ์ ์งํ๋ค. queued/submitted๋ ์๋ฃ๊ฐ ์๋๋ฉฐ ax_result๋ก ํ์ธํ๋ค. unknown์ ์ฌ์ ์กํ์ง ์๋๋ค. ์ผ๋ฐ ์งํ ์๋ฆผ์๋ ์ ํด ์๋ ax_message๋ฅผ ์ฌ์ฉํ๋ค. ์์
์ค์ธ ์ธ์
์ ๊ฐ์ ์ค๋จํ์ง ์๋๋ค.',
|
|
497
512
|
inputSchema: { type: 'object', properties: {
|
|
498
513
|
to: { type: 'string', description: 'ax_sessions์์ ํ์ธํ ์๋ ์ธ์
UUID' },
|
|
499
514
|
prompt: { type: 'string', description: '์๋๊ฐ ์คํํ ์์
์์ฒญ' },
|
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.10.0",
|
|
5
5
|
"//private": "๐ด ์ฌ๊ธฐ ์๋ \"private\": true ๋ฅผ 2026-08-28 ์ ์ง์ ๋ค. ๊ทธ ์ค์ด ์๋ ๋์ npm publish ๋ ๊ฑฐ๋ถ๋๋ค. ๋๋๋ฆฌ๋ ค๋ฉด ๋ค์ ๋ฃ์ผ๋ฉด ๋์ง๋ง, ์ด๋ฏธ ์ฌ๋ผ๊ฐ ๋ฒ์ ์ ๊ทธ๋๋ ์ ์ฌ๋ผ์ง๋ค โ npm ์ ๊ฐ์ ๋ฒํธ๋ฅผ ๋ฎ์ด์ฐ์ง ๋ชปํ๊ณ ์ญ์ ๋ 72์๊ฐ ์์๋ง ๋๋ค. ์ฆ ์ด ์ค์ ๋์ด๋ฆฌ๋ ๊ฒ์ '์์ผ๋ก ์ ์ฌ๋ฆฐ๋ค' ๋ ๋ป์ด์ง '์ฌ๋ฆฐ ๊ฒ์ ์์ค๋ค' ๋ ๋ป์ด ์๋๋ค.",
|
|
6
6
|
"//publishConfig": "axmap-cli ๋ ์ค์ฝํ๊ฐ ์์ด์ ๊ธฐ๋ณธ์ด ์ด๋ฏธ ๊ณต๊ฐ๋ค. ์ฆ ์ง๊ธ์ ์์ด๋ ๋๋ค. ๊ทธ๋๋ ๋จ๊ฒจ ๋๋ ์ด์ : ๋์ค์ @์กฐ์ง/axmap ์ฒ๋ผ ์ค์ฝํ๋ฅผ ๋ถ์ด๋ ๋ ์ด ์ค์ด ์์ผ๋ฉด publish ๊ฐ '์ ๋ฃ ํ๋์ด ํ์ํ๋ค'๋ ์๋ฑํ ๋ง๋ก ์คํจํ๋ค.",
|
|
7
7
|
"publishConfig": {
|
package/src/bridge.mjs
CHANGED
|
@@ -5,11 +5,12 @@ import fs from 'node:fs';
|
|
|
5
5
|
import os from 'node:os';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { randomUUID } from 'node:crypto';
|
|
8
|
-
import { spawnSync } from 'node:child_process';
|
|
8
|
+
import { spawn, 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.
|
|
@@ -90,6 +100,24 @@ function runAPI(session, args) {
|
|
|
90
100
|
return null;
|
|
91
101
|
}
|
|
92
102
|
function transcript(session) {
|
|
103
|
+
if (session.provider === 'claude') {
|
|
104
|
+
const home = process.env.AXMAP_CLAUDE_HOME || process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
|
|
105
|
+
const relative = session.transcript ? path.relative(path.join(home, 'projects'), session.transcript) : '..';
|
|
106
|
+
if (!session.transcript || relative.startsWith('..') || path.isAbsolute(relative) || path.basename(session.transcript) !== `${session.conversationId}.jsonl`)
|
|
107
|
+
throw new Error('Claude transcript identity mismatch');
|
|
108
|
+
if (!fs.existsSync(session.transcript))
|
|
109
|
+
return [];
|
|
110
|
+
return fs.readFileSync(session.transcript, 'utf8').split('\n').slice(0, -1).filter(Boolean).flatMap(line => {
|
|
111
|
+
const entry = JSON.parse(line);
|
|
112
|
+
if (entry.sessionId && entry.sessionId !== session.conversationId)
|
|
113
|
+
return [];
|
|
114
|
+
const content = entry.message?.content;
|
|
115
|
+
const text = typeof content === 'string' ? content : Array.isArray(content) ? content.filter(b => b.type === 'text').map(b => b.text || '').join('\n') : '';
|
|
116
|
+
if (!text || !['user', 'assistant'].includes(entry.type || ''))
|
|
117
|
+
return [];
|
|
118
|
+
return [{ type: entry.type === 'user' ? 'USER_INPUT' : 'PLANNER_RESPONSE', source: 'MODEL', status: 'DONE', content: text }];
|
|
119
|
+
});
|
|
120
|
+
}
|
|
93
121
|
const expected = path.join(agyHome(), 'brain', session.conversationId || session.id, '.system_generated', 'logs', 'transcript.jsonl');
|
|
94
122
|
if (session.transcript !== expected)
|
|
95
123
|
throw new Error('๋ฑ๋ก๋ ๋ํ ๊ธฐ๋ก ๊ฒฝ๋ก๊ฐ ์ฌ๋ฐ๋ฅด์ง ์์ต๋๋ค.');
|
|
@@ -98,26 +126,102 @@ function transcript(session) {
|
|
|
98
126
|
lines.pop();
|
|
99
127
|
return lines.filter(s => s.trim()).map(s => JSON.parse(s));
|
|
100
128
|
}
|
|
101
|
-
function publicSession(s) {
|
|
129
|
+
function publicSession(s, liveness) {
|
|
102
130
|
return { id: s.id, provider: s.provider, workspaces: s.workspaces, model: s.model,
|
|
103
131
|
conversationId: s.conversationId,
|
|
104
132
|
transport: s.transport || 'agentapi', state: s.state, pid: s.pid,
|
|
105
|
-
registeredAt: s.registeredAt, event: s.event,
|
|
106
|
-
|
|
133
|
+
registeredAt: s.registeredAt, event: s.event,
|
|
134
|
+
// ํธ์คํธ๊ฐ ์ด์ ์๋์ง ํ์ธํ์ง ๋ชปํ์ผ๋ฉด ์ ์ฅ๋ "๋๊ธฐ"๋ฅผ ๋๊ธฐ๋ก ๋ณด๊ณ ํ์ง ์๋๋ค.
|
|
135
|
+
fullyIdle: liveness === 'unverified' ? false : s.fullyIdle,
|
|
136
|
+
role: s.role, usage: s.usage, usageKnown: s.usageKnown, maxTokens: s.maxTokens, pendingApproval: s.pendingApproval,
|
|
137
|
+
...(liveness ? { liveness } : {}) };
|
|
138
|
+
}
|
|
139
|
+
/** ๊ด๋ฆฌํ ์ธ์
(ptyยทstdio)์ ๋ก์ปฌ ์ ์ด ํ์ดํ. ์ธ์
ํธ์คํธ๊ฐ ์ด์ ์๋ ๋์๋ง ์กด์ฌํ๋ค. */
|
|
140
|
+
export const hostPipe = (id) => process.platform === 'win32' ? `\\\\.\\pipe\\axmap-${id}` : path.join(bridgeHome(), `${id}.sock`);
|
|
141
|
+
/** Windows ์ ์ด๋ฆฐ ํ์ดํ ์ด๋ฆ ๋ชฉ๋ก. ์ฝ์ง ๋ชปํ๋ฉด ์ค๋ฅ ์์ฒด๋ฅผ ๋๋ ค์ค๋ค โ
|
|
142
|
+
* "๋ชฉ๋ก์ ์์"๊ณผ "๋ชฉ๋ก์ ๋ชป ์ฝ์"์ ๊ฐ์ ๊ฒ์ผ๋ก ๋ณด๋ฉด ์ฐ ์ธ์
์ ๋ซํ์ผ๋ก ์ ๋๋ค. */
|
|
143
|
+
function openPipes() {
|
|
144
|
+
try {
|
|
145
|
+
return new Set(fs.readdirSync('\\\\.\\pipe\\').map(n => n.toLowerCase()));
|
|
146
|
+
}
|
|
147
|
+
catch (e) {
|
|
148
|
+
return e instanceof Error ? e : new Error(String(e));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* ์ ์ฅ๋ ์ํ๊ฐ ์๋๋ผ ํธ์คํธ๊ฐ ์ค์ ๋ก ์ด์ ์๋์ง. ์ฐ๊ฒฐ ์์ฒญ์ ๋ณด๋ด์ง ์๋๋ค.
|
|
153
|
+
*
|
|
154
|
+
* `bridge serve` ๊ฐ ๊ฐ์ ์ข
๋ฃ๋๋ฉด ์ข
๋ฃ ์ฒ๋ฆฌ(state: closed)๋ฅผ ์ฐ์ง ๋ชปํด ๊ธฐ๋ก์ด
|
|
155
|
+
* ์์ํ `idle` ๋ก ๋จ๋๋ค (์ค์ธก: ์ธ์
d3cde187, ํ์ดํ ์์). ํ์ดํ๋ ํธ์คํธ๊ฐ
|
|
156
|
+
* listen ํ ๋ค์๋ง ๊ธฐ๋ก์ ์ฐ๋ฏ๋ก, ํ์ดํ๊ฐ ์๋ค๋ ๊ฒ์ ํธ์คํธ๊ฐ ๋๋ฌ๋ค๋ ๋ป์ด๋ค.
|
|
157
|
+
* Windows : ํ์ดํ ์ด๋ฆ ๋ชฉ๋ก์ ์์ผ๋ฉด dead, ๋ชฉ๋ก์ ๋ชป ์ฝ์ผ๋ฉด unverified
|
|
158
|
+
* ๊ทธ ๋ฐ : ์์ผ ํ์ผ์ด ์์ผ๋ฉด dead. ์์ผ๋ฉด ๊ธฐ๋ก๋ ํธ์คํธ pid ๋ก ํ๋จํ๊ณ ,
|
|
159
|
+
* pid ๊ฐ ์์ผ๋ฉด(์ด์ ๋ฒ์ ๊ธฐ๋ก) unverified โ ๋จ์ ์์ผ ํ์ผ๋ง์ผ๋ก๋ ๋ชจ๋ฅธ๋ค.
|
|
160
|
+
*/
|
|
161
|
+
function hostLiveness(s, pipes) {
|
|
162
|
+
if (process.platform === 'win32') {
|
|
163
|
+
const listed = pipes();
|
|
164
|
+
if (listed instanceof Error)
|
|
165
|
+
return 'unverified';
|
|
166
|
+
return listed.has(`axmap-${s.id}`.toLowerCase()) ? 'alive' : 'dead';
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
fs.statSync(hostPipe(s.id));
|
|
170
|
+
}
|
|
171
|
+
catch (e) {
|
|
172
|
+
return errorCode(e) === 'ENOENT' ? 'dead' : 'unverified';
|
|
173
|
+
}
|
|
174
|
+
if (!Number.isSafeInteger(s.hostPid) || s.hostPid <= 0)
|
|
175
|
+
return 'unverified';
|
|
176
|
+
try {
|
|
177
|
+
process.kill(s.hostPid, 0);
|
|
178
|
+
return 'alive';
|
|
179
|
+
}
|
|
180
|
+
catch (e) {
|
|
181
|
+
return errorCode(e) === 'ESRCH' ? 'dead' : errorCode(e) === 'EPERM' ? 'alive' : 'unverified';
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function markClosed(target, s) {
|
|
185
|
+
// ํธ์คํธ๊ฐ ๋๋ ๋ค ๊ธฐ๋ก์ ๋ฐ๊พธ๋ ์ชฝ์ ์ฌ๊ธฐ๋ฟ์ด์ง๋ง, ๋ง ์ข
๋ฃ ์ฒ๋ฆฌ ์ค์ธ ํธ์คํธ๊ฐ ๋
|
|
186
|
+
// ์ต์ ๊ธฐ๋ก(์ฌ์ฉ๋ ๋ฑ)์ ์ผ์ ์ ์์ด ๋ค์ ์ฝ๊ณ ๋ฐ๊พผ๋ค.
|
|
187
|
+
let latest = s;
|
|
188
|
+
try {
|
|
189
|
+
latest = readJSON(target);
|
|
190
|
+
}
|
|
191
|
+
catch { /* ์๋์์ ์ฝ์ ๊ธฐ๋ก์ผ๋ก ํ์๋ง ํ๋ค */ }
|
|
192
|
+
if (latest.state === 'closed')
|
|
193
|
+
return latest;
|
|
194
|
+
const closed = { ...latest, state: 'closed', fullyIdle: false,
|
|
195
|
+
closedReason: '์ธ์
ํธ์คํธ์ ๋ก์ปฌ ์ฐ๊ฒฐ ํ์ดํ๊ฐ ์์ต๋๋ค. ์ ์ ์ข
๋ฃ ์ฒ๋ฆฌ ์์ด ๋๋ ๊ฒ์ผ๋ก ๋ณด์
๋๋ค.' };
|
|
196
|
+
// ์ ์ฅ์ ์คํจํด๋ ์ด๋ฒ ์กฐํ์๋ ๋ซํ์ผ๋ก ๋ณด๊ณ ํ๋ค. ๋ค์ ์กฐํ๊ฐ ๋ค์ ์๋ํ๋ค.
|
|
197
|
+
try {
|
|
198
|
+
writeJSON(target, closed);
|
|
199
|
+
}
|
|
200
|
+
catch { /* ์์ ๊ฐ๋ค */ }
|
|
201
|
+
return closed;
|
|
107
202
|
}
|
|
108
203
|
export function sessions(cwd) {
|
|
109
204
|
const dir = path.join(bridgeHome(), 'sessions');
|
|
110
205
|
if (!fs.existsSync(dir))
|
|
111
206
|
return [];
|
|
112
207
|
const repo = repoKey(cwd);
|
|
208
|
+
let pipes;
|
|
209
|
+
const listPipes = () => (pipes ??= openPipes());
|
|
113
210
|
return fs.readdirSync(dir).filter(n => UUID.test(n.replace(/\.json$/, '')) && n.endsWith('.json'))
|
|
114
|
-
.map(n =>
|
|
211
|
+
.map(n => [path.join(dir, n), readJSON(path.join(dir, n))])
|
|
212
|
+
.filter(([, s]) => s.repos.includes(repo))
|
|
213
|
+
.map(([target, s]) => {
|
|
214
|
+
if (!['pty', 'stdio'].includes(s.transport || '') || s.state === 'closed')
|
|
215
|
+
return publicSession(s);
|
|
216
|
+
const liveness = hostLiveness(s, listPipes);
|
|
217
|
+
return liveness === 'dead' ? publicSession(markClosed(target, s), 'dead') : publicSession(s, liveness);
|
|
218
|
+
});
|
|
115
219
|
}
|
|
116
220
|
function sessionFor(id, cwd) {
|
|
117
221
|
const s = readJSON(file('sessions', id));
|
|
118
222
|
if (!s.repos.includes(repoKey(cwd)))
|
|
119
223
|
throw new Error('์ด ์ธ์
์ ํ์ฌ ์์
์ ์ฅ์์ ๋ฑ๋ก๋์ง ์์์ต๋๋ค.');
|
|
120
|
-
if (s.provider !== 'agy' && !['pty', 'stdio'].includes(s.transport || ''))
|
|
224
|
+
if (s.provider !== 'agy' && !(s.provider === 'claude' && s.transport === 'console') && !['pty', 'stdio'].includes(s.transport || ''))
|
|
121
225
|
throw new Error('์์ง ์ง์ํ์ง ์๋ ์์ ์ธ์
์
๋๋ค.');
|
|
122
226
|
return s;
|
|
123
227
|
}
|
|
@@ -149,16 +253,32 @@ export function requestResult(id, cwd) {
|
|
|
149
253
|
const request = readJSON(target);
|
|
150
254
|
if (request.repo !== repoKey(cwd))
|
|
151
255
|
throw new Error('์ด ์์ฒญ์ ํ์ฌ ์์
์ ์ฅ์์ ์์ฒญ์ด ์๋๋๋ค.');
|
|
152
|
-
if (!['submitted', 'unknown'].includes(request.state))
|
|
256
|
+
if (!['queued', 'submitted', 'unknown'].includes(request.state))
|
|
153
257
|
return request;
|
|
154
258
|
let s = sessionFor(request.to, cwd);
|
|
259
|
+
if (request.state === 'queued' && s.transport === 'console')
|
|
260
|
+
return request;
|
|
155
261
|
if (['pty', 'stdio'].includes(s.transport || '')) {
|
|
156
262
|
try {
|
|
157
263
|
Object.assign(request, terminalRPC(s.id, { action: 'result', id }));
|
|
158
264
|
}
|
|
159
265
|
catch (e) {
|
|
160
|
-
|
|
161
|
-
|
|
266
|
+
const saved = path.join(bridgeHome(), 'jobs', `${id}.json`);
|
|
267
|
+
// A normal host exit persists outcomes before closing the pipe. Never replay
|
|
268
|
+
// a queued job after a crash or mistake an unexecuted job for completion.
|
|
269
|
+
if (s.transport === 'stdio' && fs.existsSync(saved)) {
|
|
270
|
+
const job = readJSON(saved);
|
|
271
|
+
if (job.id === id && job.to === s.id && ['completed', 'failed', 'unknown'].includes(job.state))
|
|
272
|
+
Object.assign(request, job);
|
|
273
|
+
else {
|
|
274
|
+
request.state = 'unknown';
|
|
275
|
+
request.error = errorMessage(e);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
request.state = 'unknown';
|
|
280
|
+
request.error = errorMessage(e);
|
|
281
|
+
}
|
|
162
282
|
}
|
|
163
283
|
writeJSON(target, request);
|
|
164
284
|
return request;
|
|
@@ -206,7 +326,7 @@ export async function waitForRequest(id, cwd, timeoutMs = 30000) {
|
|
|
206
326
|
const deadline = performance.now() + timeoutMs;
|
|
207
327
|
for (;;) {
|
|
208
328
|
const result = requestResult(id, cwd);
|
|
209
|
-
if (
|
|
329
|
+
if (!['queued', 'sending', 'submitted'].includes(result.state) || performance.now() >= deadline)
|
|
210
330
|
return result;
|
|
211
331
|
await new Promise(resolve => setTimeout(resolve, Math.min(100, Math.max(0, deadline - performance.now()))));
|
|
212
332
|
}
|
|
@@ -216,60 +336,106 @@ export function sessionLeasePath(id) {
|
|
|
216
336
|
throw new Error('์ธ์
ID๋ UUID์ฌ์ผ ํฉ๋๋ค.');
|
|
217
337
|
return path.join(bridgeHome(), 'dialogues', `session-${id}.lock`);
|
|
218
338
|
}
|
|
219
|
-
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`), () => {
|
|
339
|
+
export function dispatchRequest({ to, prompt, cwd, from = 'axmap', dialogueId = null, queueIfBusy = false }) {
|
|
340
|
+
// ๋ณด๋ด๊ธฐ ์์ ์ ์ ๋ ์ค๋ฅ(์ด์ ์์ฒญ ๋ฏธํ์ , ๋๊ธฐ ๋ฏธํ์ธ ๋ฑ)๋ ํ์คํ ์ ๋ฌ๋์ง ์์ ๊ฒ์ด๋ค.
|
|
341
|
+
// ๋ฐ์ ํธ์งํจ์ด ์ด๋ฅผ "์ ๋ฌ ๋ถ๋ช
"๊ณผ ๊ตฌ๋ณํด์ผ ์ชฝ์ง๋ฅผ ๋ค์ ๋๊ธฐ์ด๋ก ๋๋ฆด ์ ์๋ค.
|
|
342
|
+
let sending = false;
|
|
343
|
+
try {
|
|
344
|
+
if (typeof prompt !== 'string' || !prompt.trim() || Buffer.byteLength(prompt) > 32768)
|
|
345
|
+
throw new Error('์์ฒญ ๋ณธ๋ฌธ์ 1~32768๋ฐ์ดํธ์ฌ์ผ ํฉ๋๋ค.');
|
|
346
|
+
const s = sessionFor(to, cwd);
|
|
347
|
+
const lease = sessionLeasePath(to);
|
|
229
348
|
if (fs.existsSync(lease) && readJSON(lease).dialogueId !== dialogueId)
|
|
230
|
-
throw new Error('์ด ์ธ์
์ ์๋ ๋ํ๊ฐ ์ฌ์ฉ ์ค์
๋๋ค.');
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
continue;
|
|
235
|
-
const latest = previous.state === 'sending' ? previous : requestResult(previous.id, cwd);
|
|
236
|
-
if (['sending', 'submitted', 'unknown'].includes(latest.state))
|
|
237
|
-
throw new Error(`์ด ์ธ์
์ ์ด์ ์์ฒญ์ด ์์ง ํ์ ๋์ง ์์์ต๋๋ค: ${latest.id}`);
|
|
238
|
-
}
|
|
239
|
-
// No fallback to `agy -p` or --conversation: both would start a separate process.
|
|
240
|
-
let consolePreflight;
|
|
241
|
-
if (s.transport === 'console') {
|
|
349
|
+
throw new Error('์ด ์ธ์
์ ์๋ ๋ํ๊ฐ ์ฌ์ฉ ์ค์
๋๋ค. ํด๋น ๋ํ๊ฐ ๋๋ ๋ค ์์ฒญํ์ธ์.');
|
|
350
|
+
const dir = path.join(bridgeHome(), 'requests');
|
|
351
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
352
|
+
if (queueIfBusy && s.provider === 'claude' && s.transport === 'console') {
|
|
242
353
|
validateConsolePrompt(prompt, from);
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
request.state = 'submitted';
|
|
259
|
-
}
|
|
260
|
-
catch (e) {
|
|
261
|
-
// A timeout may occur after delivery. Never retry automatically.
|
|
262
|
-
request.state = 'unknown';
|
|
263
|
-
request.error = errorMessage(e);
|
|
354
|
+
if (s.state === 'closed')
|
|
355
|
+
throw new Error('Claude terminal is closed');
|
|
356
|
+
const request = withRepoLock(path.join(dir, `${to}.lock`), () => {
|
|
357
|
+
const pending = fs.readdirSync(dir).filter(n => n.endsWith('.json')).map(n => readJSON(path.join(dir, n)))
|
|
358
|
+
.filter(r => r.to === to && r.state === 'queued');
|
|
359
|
+
if (pending.length >= 100)
|
|
360
|
+
throw new Error('Claude command queue is full');
|
|
361
|
+
const request = { id: randomUUID(), to, from, repo: repoKey(cwd), prompt, createdAt: new Date().toISOString(), baseline: null, state: 'queued' };
|
|
362
|
+
writeJSON(file('requests', request.id), request);
|
|
363
|
+
return request;
|
|
364
|
+
});
|
|
365
|
+
const worker = spawn(process.execPath, [path.join(ROOT, 'tools', 'console-queue.mjs'), to, cwd], { detached: true, windowsHide: true, stdio: 'ignore' });
|
|
366
|
+
worker.on('error', () => { });
|
|
367
|
+
worker.unref();
|
|
368
|
+
return request;
|
|
264
369
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
370
|
+
return withRepoLock(path.join(dir, `${to}.lock`), () => {
|
|
371
|
+
if (fs.existsSync(lease) && readJSON(lease).dialogueId !== dialogueId)
|
|
372
|
+
throw new Error('์ด ์ธ์
์ ์๋ ๋ํ๊ฐ ์ฌ์ฉ ์ค์
๋๋ค.');
|
|
373
|
+
for (const n of fs.readdirSync(dir).filter(n => n.endsWith('.json'))) {
|
|
374
|
+
const previous = readJSON(path.join(dir, n));
|
|
375
|
+
if (previous.to !== to || !['queued', 'sending', 'submitted', 'unknown'].includes(previous.state))
|
|
376
|
+
continue;
|
|
377
|
+
const latest = previous.state === 'sending' ? previous : requestResult(previous.id, cwd);
|
|
378
|
+
if (queueIfBusy && s.transport === 'stdio' && ['queued', 'submitted'].includes(latest.state))
|
|
379
|
+
continue;
|
|
380
|
+
if (['queued', 'sending', 'submitted', 'unknown'].includes(latest.state))
|
|
381
|
+
throw new Error(`์ด ์ธ์
์ ์ด์ ์์ฒญ์ด ์์ง ํ์ ๋์ง ์์์ต๋๋ค: ${latest.id}`);
|
|
382
|
+
}
|
|
383
|
+
// No fallback to `agy -p` or --conversation: both would start a separate process.
|
|
384
|
+
let consolePreflight;
|
|
385
|
+
if (s.transport === 'console') {
|
|
386
|
+
validateConsolePrompt(prompt, from);
|
|
387
|
+
consolePreflight = checkConsole(s);
|
|
388
|
+
}
|
|
389
|
+
if (!['pty', 'stdio', 'console'].includes(s.transport || '')) {
|
|
390
|
+
runAPI(s, ['get-conversation-metadata', to]);
|
|
391
|
+
if (!s.fullyIdle || s.event !== 'Stop')
|
|
392
|
+
throw new Error('์๋ ์ธ์
์ ๋๊ธฐ ์ํ๋ฅผ ์์ง ํ์ธํ์ง ๋ชปํ์ต๋๋ค. ์์
์ข
๋ฃ ํ
์ ๊ธฐ๋ค๋ฆฌ์ธ์.');
|
|
393
|
+
}
|
|
394
|
+
const request = { id: randomUUID(), to, from, repo: repoKey(cwd), prompt,
|
|
395
|
+
createdAt: new Date().toISOString(), baseline: ['pty', 'stdio'].includes(s.transport || '') ? null : s.transcript ? transcript(s).length : 0, state: 'sending' };
|
|
396
|
+
const target = file('requests', request.id);
|
|
397
|
+
writeJSON(target, request);
|
|
398
|
+
sending = true;
|
|
399
|
+
try {
|
|
400
|
+
request.receipt = s.transport === 'console' ? submitConsole(s, request, consolePreflight) : ['pty', 'stdio'].includes(s.transport || '')
|
|
401
|
+
? terminalRPC(to, { action: 'submit', request, ...(queueIfBusy && s.transport === 'stdio' ? { queueIfBusy: true } : {}) })
|
|
402
|
+
: runAPI(s, ['send-message', to, `[axMap request ${request.id}]\n์์ฒญ์: ${from}\n${prompt}`]);
|
|
403
|
+
request.state = request.receipt?.queued ? 'queued' : 'submitted';
|
|
404
|
+
}
|
|
405
|
+
catch (e) {
|
|
406
|
+
if (notDelivered(e)) {
|
|
407
|
+
// ๋ก์ปฌ ํ์ดํ์ ์ฐ๊ฒฐ์กฐ์ฐจ ๋ชป ํ๋ค(ENOENTยทECONNREFUSED). ํ ๋ฐ์ดํธ๋ ๋ณด๋ด์ง
|
|
408
|
+
// ์์์ผ๋ฏ๋ก ํ์คํ ์ ๋ฌ๋์ง ์์๋ค. unknown ์ผ๋ก ๋๋ฉด ์ด ์ธ์
์ ๋ค์ ์์ฒญ์ด
|
|
409
|
+
// ์ ๋ถ ๋งํ๋ค. ์๋ ์ฌ์ ์ก์ ์ฌ์ ํ ํ์ง ์๋๋ค.
|
|
410
|
+
request.state = 'failed';
|
|
411
|
+
request.error = `์ ๋ฌ๋์ง ์์์ต๋๋ค: ${errorMessage(e)}`;
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
// A timeout may occur after delivery. Never retry automatically.
|
|
415
|
+
request.state = 'unknown';
|
|
416
|
+
request.error = errorMessage(e);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
writeJSON(target, request);
|
|
420
|
+
return request;
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
catch (e) {
|
|
424
|
+
if (!sending && e instanceof Error)
|
|
425
|
+
Object.assign(e, { notDelivered: true });
|
|
426
|
+
throw e;
|
|
427
|
+
}
|
|
268
428
|
}
|
|
429
|
+
/** ์ฐ๊ฒฐ ์ ์ ์คํจํด ์๋ฌด๊ฒ๋ ๋ณด๋ด์ง ์์ ์ค๋ฅ๋ง true. ๋ฌธ๊ตฌ๊ฐ ์๋๋ผ ์ข
๋ฃ ์ฝ๋๋ ๋ณด๋ด๊ธฐ ์ ๋จ๊ณ๋ก ๊ฐ๋ฅธ๋ค. */
|
|
430
|
+
export const notDelivered = (e) => e instanceof Error && e.notDelivered === true;
|
|
269
431
|
function terminalRPC(id, message) {
|
|
270
432
|
const r = spawnSync(process.execPath, [path.join(ROOT, 'tools', 'run.mjs'), 'rpc', id], {
|
|
271
433
|
input: JSON.stringify(message), encoding: 'utf8', windowsHide: true, timeout: 15000,
|
|
272
434
|
});
|
|
435
|
+
// ์๊ฐ ์ด๊ณผ(r.error)๋ ๊ทธ ๋ฐ์ ์คํจ๋ ์ฐ๊ฒฐ ๋ค์ ๋ฌ์ ์ ์์ด ์ ๋ฌ ์ฌ๋ถ๋ฅผ ๋ชจ๋ฅธ๋ค.
|
|
436
|
+
if (!r.error && r.status === RPC_NOT_DELIVERED_EXIT) {
|
|
437
|
+
throw Object.assign(new Error(r.stderr?.trim() || '๋ก์ปฌ ์ธ์
ํ์ดํ์ ์ฐ๊ฒฐํ์ง ๋ชปํ์ต๋๋ค.'), { notDelivered: true });
|
|
438
|
+
}
|
|
273
439
|
if (r.error || r.status !== 0)
|
|
274
440
|
throw new Error(r.error?.message || r.stderr?.trim() || 'ํฐ๋ฏธ๋ ์ธ์
์ ์ฐ๊ฒฐํ์ง ๋ชปํ์ต๋๋ค.');
|
|
275
441
|
return JSON.parse(r.stdout);
|
|
@@ -333,3 +499,89 @@ export function installHooks({ executable, home = os.homedir() }) {
|
|
|
333
499
|
return { hooks: target, backup: original === null ? null : backup,
|
|
334
500
|
note: '๊ธฐ์กด ์คํ ์ค์ธ CLI๊ฐ ์ ํ
์ ์ฝ์ง ์์ผ๋ฉด ๊ฐ์ ๋ํ๋ฅผ ์ฌ๊ฐํด ํ ๋ฒ ๋ค์ ์ด์ด์ผ ํฉ๋๋ค. Remote Control์ ํ์ ์์ต๋๋ค.' };
|
|
335
501
|
}
|
|
502
|
+
/** One local worker per ordinary Claude terminal; no provider is launched. */
|
|
503
|
+
export function runConsoleQueue(to, cwd) {
|
|
504
|
+
const dir = path.join(bridgeHome(), 'requests');
|
|
505
|
+
const runnerLock = path.join(dir, `${to}.runner.lock`);
|
|
506
|
+
if (fs.existsSync(runnerLock)) {
|
|
507
|
+
const owner = readJSON(runnerLock);
|
|
508
|
+
try {
|
|
509
|
+
process.kill(owner.pid, 0);
|
|
510
|
+
}
|
|
511
|
+
catch (e) {
|
|
512
|
+
if (errorCode(e) === 'ESRCH')
|
|
513
|
+
fs.unlinkSync(runnerLock);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
try {
|
|
517
|
+
withRepoLock(runnerLock, () => {
|
|
518
|
+
for (;;) {
|
|
519
|
+
const records = withRepoLock(path.join(dir, `${to}.lock`), () => fs.readdirSync(dir)
|
|
520
|
+
.filter(n => n.endsWith('.json')).map(n => readJSON(path.join(dir, n)))
|
|
521
|
+
.filter(r => r.to === to).sort((a, b) => a.createdAt.localeCompare(b.createdAt)));
|
|
522
|
+
const queued = records.filter(r => r.state === 'queued');
|
|
523
|
+
if (!queued.length)
|
|
524
|
+
return;
|
|
525
|
+
let waiting = false;
|
|
526
|
+
for (const r of records.filter(r => ['sending', 'submitted', 'unknown'].includes(r.state))) {
|
|
527
|
+
const latest = r.state === 'sending' ? r : requestResult(r.id, cwd);
|
|
528
|
+
if (['unknown', 'sending'].includes(latest.state))
|
|
529
|
+
return;
|
|
530
|
+
if (latest.state === 'submitted')
|
|
531
|
+
waiting = true;
|
|
532
|
+
}
|
|
533
|
+
const s = sessionFor(to, cwd);
|
|
534
|
+
if (s.pid) {
|
|
535
|
+
try {
|
|
536
|
+
process.kill(s.pid, 0);
|
|
537
|
+
}
|
|
538
|
+
catch (e) {
|
|
539
|
+
if (errorCode(e) === 'ESRCH') {
|
|
540
|
+
for (const r of queued)
|
|
541
|
+
writeJSON(file('requests', r.id), { ...r, state: 'failed', error: 'Claude process exited before execution' });
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
if (s.state === 'closed') {
|
|
547
|
+
for (const r of queued)
|
|
548
|
+
writeJSON(file('requests', r.id), { ...r, state: 'failed', error: 'Claude terminal closed before execution' });
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
let live;
|
|
552
|
+
try {
|
|
553
|
+
live = checkConsole(s);
|
|
554
|
+
}
|
|
555
|
+
catch {
|
|
556
|
+
waiting = true;
|
|
557
|
+
}
|
|
558
|
+
if (waiting || !live) {
|
|
559
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 200);
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
withRepoLock(path.join(dir, `${to}.lock`), () => {
|
|
563
|
+
const r = readJSON(file('requests', queued[0].id));
|
|
564
|
+
if (r.state !== 'queued')
|
|
565
|
+
return;
|
|
566
|
+
r.baseline = transcript(s).length;
|
|
567
|
+
r.state = 'sending';
|
|
568
|
+
writeJSON(file('requests', r.id), r);
|
|
569
|
+
try {
|
|
570
|
+
r.receipt = submitConsole(s, r, live);
|
|
571
|
+
r.state = 'submitted';
|
|
572
|
+
}
|
|
573
|
+
catch (e) {
|
|
574
|
+
r.state = notDelivered(e) ? 'queued' : 'unknown';
|
|
575
|
+
r.error = errorMessage(e);
|
|
576
|
+
}
|
|
577
|
+
writeJSON(file('requests', r.id), r);
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
}, 1000);
|
|
581
|
+
}
|
|
582
|
+
catch (e) {
|
|
583
|
+
// Another worker won the runner lease. It owns draining; never replay here.
|
|
584
|
+
if (!fs.existsSync(runnerLock))
|
|
585
|
+
throw e;
|
|
586
|
+
}
|
|
587
|
+
}
|