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.
Files changed (46) hide show
  1. package/README.ko.md +36 -0
  2. package/README.md +36 -0
  3. package/bin/axmap.mjs +6 -0
  4. package/mcp/README.md +6 -0
  5. package/mcp/pipeline.html +157 -0
  6. package/mcp/server.mjs +130 -20
  7. package/package.json +11 -3
  8. package/src/agentcli.mjs +17 -14
  9. package/src/agentcli.mts +19 -0
  10. package/src/bridge.mjs +299 -210
  11. package/src/bridge.mts +281 -0
  12. package/src/external-console.mjs +159 -0
  13. package/src/external-console.mts +128 -0
  14. package/src/managed-session.mjs +263 -169
  15. package/src/managed-session.mts +198 -0
  16. package/src/monitor-cli.mjs +87 -0
  17. package/src/monitor-cli.mts +64 -0
  18. package/src/monitor.mjs +240 -0
  19. package/src/monitor.mts +195 -0
  20. package/src/node-pty.d.ts +13 -0
  21. package/src/pipeline-mcp.mjs +109 -0
  22. package/src/pipeline-mcp.mts +103 -0
  23. package/src/pipeline-statusline.mjs +135 -0
  24. package/src/pipeline-statusline.mts +106 -0
  25. package/src/session-dialogue.mjs +168 -116
  26. package/src/session-dialogue.mts +136 -0
  27. package/src/session-status.mjs +54 -0
  28. package/src/session-status.mts +50 -0
  29. package/src/session-types.mjs +7 -0
  30. package/src/session-types.mts +118 -0
  31. package/src/session-usage.mjs +33 -28
  32. package/src/session-usage.mts +39 -0
  33. package/src/terminal-bridge.mjs +294 -209
  34. package/src/terminal-bridge.mts +235 -0
  35. package/src/windows-console.mjs +42 -0
  36. package/src/windows-console.mts +29 -0
  37. package/tools/bench-console.mjs +70 -0
  38. package/tools/bridge.mjs +20 -6
  39. package/tools/build.mjs +39 -0
  40. package/tools/monitor.mjs +5 -0
  41. package/tools/pipeline.mjs +26 -0
  42. package/tools/run-tests.mjs +3 -2
  43. package/tools/run.mjs +6 -2
  44. package/tools/setup.mjs +1 -1
  45. package/tools/windows-console.cs +116 -0
  46. 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
+ }
@@ -1,34 +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
- 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
- if (!raw) return null
9
- const u = provider === 'codex' ? {
10
- inputTokens: raw.inputTokens, cachedInputTokens: raw.cachedInputTokens,
11
- outputTokens: raw.outputTokens, reasoningTokens: raw.reasoningOutputTokens,
12
- totalTokens: raw.totalTokens, providerTotalTokens: raw.totalTokens,
13
- } : {
14
- inputTokens: raw.input_tokens, cachedInputTokens: raw.cache_read_tokens,
15
- outputTokens: raw.output_tokens, reasoningTokens: raw.thinking_tokens,
16
- totalTokens: count(raw.total_tokens) && count(raw.cache_read_tokens) ? raw.total_tokens + raw.cache_read_tokens : null,
17
- providerTotalTokens: raw.total_tokens,
18
- }
19
- if (fields.some(key => !count(u[key]))) return null
20
- if (provider === 'agy') u.inputTokens += u.cachedInputTokens
21
- if (u.cachedInputTokens > u.inputTokens || u.reasoningTokens > u.outputTokens) return null
22
- return u
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
- if (!current) return null
27
- const delta = Object.fromEntries(fields.map(k => [k, current[k] - (previous?.[k] || 0)]))
28
- return fields.every(k => count(delta[k])) ? delta : null
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
- if (rows.some(row => !row)) return null
33
- return Object.fromEntries(fields.map(k => [k, rows.reduce((sum, row) => sum + row[k], 0)]))
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
+ }