axmap-cli 1.4.0 → 1.6.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/README.ko.md +36 -0
- package/README.md +36 -0
- package/bin/axmap.mjs +6 -0
- package/mcp/README.md +6 -0
- package/mcp/pipeline.html +157 -0
- package/mcp/server.mjs +130 -20
- package/package.json +11 -3
- package/src/agentcli.mjs +17 -14
- package/src/agentcli.mts +19 -0
- package/src/bridge.mjs +299 -210
- package/src/bridge.mts +281 -0
- package/src/external-console.mjs +159 -0
- package/src/external-console.mts +128 -0
- package/src/managed-session.mjs +263 -169
- package/src/managed-session.mts +198 -0
- package/src/monitor-cli.mjs +87 -0
- package/src/monitor-cli.mts +64 -0
- package/src/monitor.mjs +240 -0
- package/src/monitor.mts +195 -0
- package/src/node-pty.d.ts +13 -0
- package/src/pipeline-mcp.mjs +109 -0
- package/src/pipeline-mcp.mts +103 -0
- package/src/pipeline-statusline.mjs +135 -0
- package/src/pipeline-statusline.mts +106 -0
- package/src/session-dialogue.mjs +168 -116
- package/src/session-dialogue.mts +136 -0
- package/src/session-status.mjs +54 -0
- package/src/session-status.mts +50 -0
- package/src/session-types.mjs +7 -0
- package/src/session-types.mts +118 -0
- package/src/session-usage.mjs +33 -28
- package/src/session-usage.mts +39 -0
- package/src/terminal-bridge.mjs +294 -209
- package/src/terminal-bridge.mts +235 -0
- package/src/windows-console.mjs +42 -0
- package/src/windows-console.mts +29 -0
- package/tools/bench-console.mjs +70 -0
- package/tools/bridge.mjs +20 -6
- package/tools/build.mjs +39 -0
- package/tools/monitor.mjs +5 -0
- package/tools/pipeline.mjs +26 -0
- package/tools/run-tests.mjs +3 -2
- package/tools/run.mjs +6 -2
- package/tools/setup.mjs +1 -1
- package/tools/windows-console.cs +116 -0
- package/tools/windows-console.ps1 +91 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { sessions } from './bridge.mjs'
|
|
2
|
+
import { sendTerminal } from './terminal-bridge.mjs'
|
|
3
|
+
import { externalStatus } from './external-console.mjs'
|
|
4
|
+
import type { PublicSession, SessionSnapshot, SessionState, ConnectionState } from './session-types.mjs'
|
|
5
|
+
|
|
6
|
+
/** Probe only the authenticated local control pipe. A saved idle/running record
|
|
7
|
+
* cannot establish that its host still exists; no model request is submitted. */
|
|
8
|
+
export async function sessionStatus(cwd = process.cwd()): Promise<SessionSnapshot[]> {
|
|
9
|
+
const listed = sessions(cwd)
|
|
10
|
+
return Promise.all(listed.map(async session => {
|
|
11
|
+
const base = { ...session, lastKnownState: session.state ?? null }
|
|
12
|
+
const snapshot = (state: SessionState, connection: ConnectionState, extra: Partial<PublicSession> = {}): SessionSnapshot => ({ ...base, ...extra, state, connection,
|
|
13
|
+
fullyIdle: connection === 'connected' && state === 'idle', checkedAt: new Date().toISOString() })
|
|
14
|
+
if (session.state === 'closed') return snapshot('closed', 'closed')
|
|
15
|
+
if (session.transport === 'console') {
|
|
16
|
+
try {
|
|
17
|
+
const live = externalStatus(session)
|
|
18
|
+
return snapshot(live.state, 'connected')
|
|
19
|
+
} catch { return snapshot('unknown', 'unreachable') }
|
|
20
|
+
}
|
|
21
|
+
if (!['pty', 'stdio'].includes(session.transport || '')) return snapshot('unknown', 'unverified')
|
|
22
|
+
try {
|
|
23
|
+
const raw: unknown = await sendTerminal(session.id, { action: 'status' }, null, { timeoutMs: 1000 })
|
|
24
|
+
const live = raw as Partial<PublicSession> | null
|
|
25
|
+
if (live?.id !== session.id || !live.state || !['starting', 'running', 'idle', 'awaiting_user', 'settling', 'closed'].includes(live.state)) {
|
|
26
|
+
return snapshot('unknown', 'unreachable')
|
|
27
|
+
}
|
|
28
|
+
return snapshot(live.state, 'connected', {
|
|
29
|
+
usage: live.usage === undefined ? session.usage : live.usage,
|
|
30
|
+
pendingApproval: live.pendingApproval,
|
|
31
|
+
})
|
|
32
|
+
} catch {
|
|
33
|
+
// A disconnected host or a timeout is not proof of a completed task.
|
|
34
|
+
return snapshot('unknown', 'unreachable')
|
|
35
|
+
}
|
|
36
|
+
}))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const labels = { starting: '시작 중', running: '작업 중', idle: '대기', awaiting_user: '승인 대기',
|
|
40
|
+
settling: '대기 전환 중', closed: '종료', unknown: '상태 미확인' }
|
|
41
|
+
const connections = { connected: '연결됨', closed: '종료됨', unreachable: '연결 확인 불가', unverified: '확인 미지원' }
|
|
42
|
+
const safe = (value: unknown) => String(value ?? '').replace(/[\x00-\x1f\x7f-\x9f]/g, ' ')
|
|
43
|
+
|
|
44
|
+
export function formatSessionStatus(listed: readonly SessionSnapshot[]): string {
|
|
45
|
+
if (!listed.length) return '이 저장소에 등록된 로컬 세션이 없습니다.'
|
|
46
|
+
return ['axMap 세션 상태', ...listed.map(s => {
|
|
47
|
+
const usage = s.usage?.totalTokens == null ? '미확인' : `${s.usage.totalTokens}${s.maxTokens ? ` / ${s.maxTokens}` : ''}`
|
|
48
|
+
return `${safe(s.provider)} ${safe(s.id)}\n ${connections[s.connection] || '상태 미확인'} · ${labels[s.state] || '상태 미확인'}${s.role ? ` · ${safe(s.role)}` : ''}\n 확인된 누적 처리 토큰: ${usage}`
|
|
49
|
+
})].join('\n')
|
|
50
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Generated from src/session-types.mts. Edit the .mts source and run npm run build.
|
|
2
|
+
export function errorMessage(error) {
|
|
3
|
+
return error instanceof Error ? error.message : String(error);
|
|
4
|
+
}
|
|
5
|
+
export function errorCode(error) {
|
|
6
|
+
return error instanceof Error && 'code' in error && typeof error.code === 'string' ? error.code : undefined;
|
|
7
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/** Shared contracts for the local bridge. Runtime input checks still guard external data. */
|
|
2
|
+
export type Provider = 'codex' | 'agy'
|
|
3
|
+
export type Transport = 'console' | 'stdio' | 'pty' | 'agentapi'
|
|
4
|
+
export type SessionState = 'starting' | 'running' | 'idle' | 'awaiting_user' | 'settling' | 'closed' | 'unknown'
|
|
5
|
+
export type RequestState = 'sending' | 'submitted' | 'unknown' | 'completed' | 'failed' | 'superseded'
|
|
6
|
+
export type ConnectionState = 'connected' | 'closed' | 'unreachable' | 'unverified'
|
|
7
|
+
export type HookEvent = 'SessionStart' | 'PreInvocation' | 'Stop'
|
|
8
|
+
|
|
9
|
+
export interface Usage {
|
|
10
|
+
inputTokens: number
|
|
11
|
+
cachedInputTokens: number
|
|
12
|
+
outputTokens: number
|
|
13
|
+
reasoningTokens: number
|
|
14
|
+
totalTokens: number
|
|
15
|
+
providerTotalTokens: number
|
|
16
|
+
}
|
|
17
|
+
export interface PendingApproval { id: string | number; method?: string; [key: string]: unknown }
|
|
18
|
+
export interface SessionRecord {
|
|
19
|
+
id: string
|
|
20
|
+
provider: Provider
|
|
21
|
+
transport?: Transport
|
|
22
|
+
workspaces: string[]
|
|
23
|
+
repos: string[]
|
|
24
|
+
state?: SessionState
|
|
25
|
+
pid?: number
|
|
26
|
+
startedAt?: string
|
|
27
|
+
registeredAt: string
|
|
28
|
+
event?: string
|
|
29
|
+
fullyIdle?: boolean
|
|
30
|
+
conversationId?: string
|
|
31
|
+
transcript?: string
|
|
32
|
+
model?: string | null
|
|
33
|
+
role?: string
|
|
34
|
+
usage?: Usage | null
|
|
35
|
+
usageKnown?: boolean
|
|
36
|
+
maxTokens?: number
|
|
37
|
+
pendingApproval?: PendingApproval
|
|
38
|
+
executable?: string
|
|
39
|
+
address?: string
|
|
40
|
+
token?: string
|
|
41
|
+
}
|
|
42
|
+
export type PublicSession = Pick<SessionRecord, 'id' | 'provider' | 'workspaces' | 'model' | 'transport' |
|
|
43
|
+
'state' | 'pid' | 'registeredAt' | 'event' | 'fullyIdle' | 'role' | 'usage' | 'usageKnown' | 'maxTokens' | 'pendingApproval'>
|
|
44
|
+
export interface SessionSnapshot extends PublicSession {
|
|
45
|
+
lastKnownState: SessionState | null
|
|
46
|
+
state: SessionState
|
|
47
|
+
connection: ConnectionState
|
|
48
|
+
checkedAt: string
|
|
49
|
+
}
|
|
50
|
+
export interface RequestRecord {
|
|
51
|
+
id: string
|
|
52
|
+
to: string
|
|
53
|
+
from: string
|
|
54
|
+
repo: string
|
|
55
|
+
prompt: string
|
|
56
|
+
createdAt: string
|
|
57
|
+
baseline: number | null
|
|
58
|
+
state: RequestState
|
|
59
|
+
receipt?: unknown
|
|
60
|
+
result?: unknown
|
|
61
|
+
error?: string
|
|
62
|
+
completedAt?: string
|
|
63
|
+
conversationId?: string
|
|
64
|
+
usage?: Usage | null
|
|
65
|
+
}
|
|
66
|
+
export interface DispatchOptions { to: string; prompt: string; cwd: string; from?: string; dialogueId?: string | null }
|
|
67
|
+
export interface HookInput {
|
|
68
|
+
conversationId?: string
|
|
69
|
+
workspacePaths?: string[]
|
|
70
|
+
transcriptPath?: string
|
|
71
|
+
modelName?: string
|
|
72
|
+
fullyIdle?: boolean
|
|
73
|
+
model?: string
|
|
74
|
+
session_id?: string
|
|
75
|
+
prompt?: string
|
|
76
|
+
last_assistant_message?: unknown
|
|
77
|
+
}
|
|
78
|
+
export type LocalRequest = Pick<RequestRecord, 'id' | 'to' | 'prompt'> & Partial<Pick<RequestRecord, 'from'>>
|
|
79
|
+
export type LocalMessage = { token?: string } & (
|
|
80
|
+
{ action: 'status' } | { action: 'result'; id: string } |
|
|
81
|
+
{ action: 'answer'; id: string | number; decision: string } |
|
|
82
|
+
{ action: 'submit'; request: LocalRequest } |
|
|
83
|
+
{ action: 'hook'; event: string; input: HookInput }
|
|
84
|
+
)
|
|
85
|
+
export interface JobRecord {
|
|
86
|
+
id: string; to: string; state: RequestState; result?: unknown; error?: string; completedAt?: string
|
|
87
|
+
baseline?: Usage | null; baselineKnown?: boolean; usage?: Usage | null; conversationId?: string
|
|
88
|
+
turnId?: string; rawUsage?: unknown; observed?: boolean; interrupted?: boolean
|
|
89
|
+
}
|
|
90
|
+
export interface TranscriptEntry { type?: string; source?: string; status?: string; content?: unknown }
|
|
91
|
+
export interface ConsoleSnapshot { pid: number; startedAt: string; screen: string; ignored?: false }
|
|
92
|
+
export interface DialogueTurn {
|
|
93
|
+
index: number; to: string; from: string; requestId: string; state: RequestState
|
|
94
|
+
usage?: Usage | null; conversationId?: string; error?: string; message?: string; done?: boolean
|
|
95
|
+
}
|
|
96
|
+
export type DialogueState = 'queued' | 'running' | 'stopping' | 'failed' | 'cancelled' | 'token_limit' |
|
|
97
|
+
'unknown' | 'usage_unavailable' | 'message_limit' | 'invalid_response' | 'completed' | 'turn_limit'
|
|
98
|
+
export interface DialogueRecord {
|
|
99
|
+
id: string; repo: string; left?: string; right?: string; state: DialogueState; createdAt?: string
|
|
100
|
+
limits?: { maxTurns: number; maxTokens: number; maxMessageChars: number; timeoutMs: number }
|
|
101
|
+
turns: DialogueTurn[]; usage?: Usage | null; knownUsage?: Usage | null; usagePending?: boolean
|
|
102
|
+
reason?: string; endedAt?: string
|
|
103
|
+
}
|
|
104
|
+
export interface DialogueOptions {
|
|
105
|
+
id?: string; left: string; right: string; prompt: string; cwd?: string
|
|
106
|
+
maxTurns?: number; maxTokens?: number; maxMessageChars?: number; timeoutMs?: number
|
|
107
|
+
onEvent?: (event: Record<string, unknown>) => void
|
|
108
|
+
dispatch?: (options: DispatchOptions) => RequestRecord
|
|
109
|
+
result?: (id: string, cwd: string) => RequestRecord
|
|
110
|
+
inventory?: (cwd: string) => PublicSession[]
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function errorMessage(error: unknown): string {
|
|
114
|
+
return error instanceof Error ? error.message : String(error)
|
|
115
|
+
}
|
|
116
|
+
export function errorCode(error: unknown): string | undefined {
|
|
117
|
+
return error instanceof Error && 'code' in error && typeof error.code === 'string' ? error.code : undefined
|
|
118
|
+
}
|
package/src/session-usage.mjs
CHANGED
|
@@ -1,34 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const count = value => Number.isSafeInteger(value) && value >= 0
|
|
5
|
-
const fields = ['inputTokens', 'cachedInputTokens', 'outputTokens', 'reasoningTokens', 'totalTokens', 'providerTotalTokens']
|
|
6
|
-
|
|
1
|
+
// Generated from src/session-usage.mts. Edit the .mts source and run npm run build.
|
|
2
|
+
const count = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
|
|
3
|
+
const fields = ['inputTokens', 'cachedInputTokens', 'outputTokens', 'reasoningTokens', 'totalTokens', 'providerTotalTokens'];
|
|
7
4
|
export function normalizeUsage(provider, raw) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
5
|
+
if (!raw || typeof raw !== 'object')
|
|
6
|
+
return null;
|
|
7
|
+
const counters = raw;
|
|
8
|
+
const candidate = provider === 'codex' ? {
|
|
9
|
+
inputTokens: counters.inputTokens, cachedInputTokens: counters.cachedInputTokens,
|
|
10
|
+
outputTokens: counters.outputTokens, reasoningTokens: counters.reasoningOutputTokens,
|
|
11
|
+
totalTokens: counters.totalTokens, providerTotalTokens: counters.totalTokens,
|
|
12
|
+
} : {
|
|
13
|
+
inputTokens: counters.input_tokens, cachedInputTokens: counters.cache_read_tokens,
|
|
14
|
+
outputTokens: counters.output_tokens, reasoningTokens: counters.thinking_tokens,
|
|
15
|
+
totalTokens: count(counters.total_tokens) && count(counters.cache_read_tokens) ? counters.total_tokens + counters.cache_read_tokens : null,
|
|
16
|
+
providerTotalTokens: counters.total_tokens,
|
|
17
|
+
};
|
|
18
|
+
if (fields.some(key => !count(candidate[key])))
|
|
19
|
+
return null;
|
|
20
|
+
// All six fields have been checked above, including provider-specific counters.
|
|
21
|
+
const u = candidate;
|
|
22
|
+
if (provider === 'agy')
|
|
23
|
+
u.inputTokens += u.cachedInputTokens;
|
|
24
|
+
if (u.cachedInputTokens > u.inputTokens || u.reasoningTokens > u.outputTokens)
|
|
25
|
+
return null;
|
|
26
|
+
return u;
|
|
23
27
|
}
|
|
24
|
-
|
|
25
28
|
export function usageDelta(current, previous = null) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
if (!current)
|
|
30
|
+
return null;
|
|
31
|
+
const delta = Object.fromEntries(fields.map(k => [k, current[k] - (previous?.[k] || 0)]));
|
|
32
|
+
return fields.every(k => count(delta[k])) ? delta : null;
|
|
29
33
|
}
|
|
30
|
-
|
|
31
34
|
export function sumUsage(rows) {
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
const known = rows.filter((row) => Boolean(row));
|
|
36
|
+
if (known.length !== rows.length)
|
|
37
|
+
return null;
|
|
38
|
+
return Object.fromEntries(fields.map(k => [k, known.reduce((sum, row) => sum + row[k], 0)]));
|
|
34
39
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Provider counters are cumulative. Cached input and reasoning are subsets in
|
|
2
|
+
* Codex, while agy reports cached reads separately from its input/total. Keep
|
|
3
|
+
* both the provider total and all processed tokens; never add reasoning twice. */
|
|
4
|
+
import type { Usage, Provider } from './session-types.mjs'
|
|
5
|
+
const count = (value: unknown): value is number => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0
|
|
6
|
+
const fields = ['inputTokens', 'cachedInputTokens', 'outputTokens', 'reasoningTokens', 'totalTokens', 'providerTotalTokens'] as const
|
|
7
|
+
|
|
8
|
+
export function normalizeUsage(provider: Provider, raw: unknown): Usage | null {
|
|
9
|
+
if (!raw || typeof raw !== 'object') return null
|
|
10
|
+
const counters = raw as Record<string, unknown>
|
|
11
|
+
const candidate = provider === 'codex' ? {
|
|
12
|
+
inputTokens: counters.inputTokens, cachedInputTokens: counters.cachedInputTokens,
|
|
13
|
+
outputTokens: counters.outputTokens, reasoningTokens: counters.reasoningOutputTokens,
|
|
14
|
+
totalTokens: counters.totalTokens, providerTotalTokens: counters.totalTokens,
|
|
15
|
+
} : {
|
|
16
|
+
inputTokens: counters.input_tokens, cachedInputTokens: counters.cache_read_tokens,
|
|
17
|
+
outputTokens: counters.output_tokens, reasoningTokens: counters.thinking_tokens,
|
|
18
|
+
totalTokens: count(counters.total_tokens) && count(counters.cache_read_tokens) ? counters.total_tokens + counters.cache_read_tokens : null,
|
|
19
|
+
providerTotalTokens: counters.total_tokens,
|
|
20
|
+
}
|
|
21
|
+
if (fields.some(key => !count(candidate[key]))) return null
|
|
22
|
+
// All six fields have been checked above, including provider-specific counters.
|
|
23
|
+
const u = candidate as Usage
|
|
24
|
+
if (provider === 'agy') u.inputTokens += u.cachedInputTokens
|
|
25
|
+
if (u.cachedInputTokens > u.inputTokens || u.reasoningTokens > u.outputTokens) return null
|
|
26
|
+
return u
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function usageDelta(current: Usage | null, previous: Usage | null = null): Usage | null {
|
|
30
|
+
if (!current) return null
|
|
31
|
+
const delta = Object.fromEntries(fields.map(k => [k, current[k] - (previous?.[k] || 0)])) as unknown as Usage
|
|
32
|
+
return fields.every(k => count(delta[k])) ? delta : null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function sumUsage(rows: ReadonlyArray<Usage | null | undefined>): Usage | null {
|
|
36
|
+
const known = rows.filter((row): row is Usage => Boolean(row))
|
|
37
|
+
if (known.length !== rows.length) return null
|
|
38
|
+
return Object.fromEntries(fields.map(k => [k, known.reduce((sum, row) => sum + row[k], 0)])) as unknown as Usage
|
|
39
|
+
}
|