@office-xyz/claude-code 0.1.8 → 0.1.9
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/index.js +109 -28
- package/onboarding.js +91 -26
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -37,6 +37,13 @@ const TOOL_NAME_MAP = {
|
|
|
37
37
|
execute_command: 'Bash', read_file: 'Read', write_file: 'Write', edit_file: 'Edit',
|
|
38
38
|
search: 'Grep', run_command: 'Bash', code_edit: 'FileEdit', code_search: 'Grep',
|
|
39
39
|
terminal: 'Bash', file_operation: 'FileEdit',
|
|
40
|
+
// Claude server-side tools (content_block type → display name)
|
|
41
|
+
server_tool_use: 'ServerTool', web_search_tool_result: 'WebSearch',
|
|
42
|
+
web_fetch_tool_result: 'WebFetch', code_execution_tool_result: 'CodeExecution',
|
|
43
|
+
mcp_tool_use: 'Mcp', mcp_tool_result: 'Mcp',
|
|
44
|
+
bash_code_execution_tool_result: 'Bash',
|
|
45
|
+
text_editor_code_execution_tool_result: 'Edit',
|
|
46
|
+
tool_search_tool_result: 'ToolSearch', container_upload: 'Upload',
|
|
40
47
|
// General aliases
|
|
41
48
|
bash: 'Bash', grep: 'Grep', glob: 'Glob', ls: 'LS',
|
|
42
49
|
}
|
|
@@ -257,16 +264,28 @@ const model = argv.model || providerConfig.defaultModel
|
|
|
257
264
|
|
|
258
265
|
// ── Session tracking ───────────────────────────────────────────────────────
|
|
259
266
|
// Map VO sessionId → Claude session_id for conversation continuity.
|
|
260
|
-
//
|
|
267
|
+
// Persisted to disk so sessions survive clock-out / clock-in cycles.
|
|
268
|
+
// --resume and --append-system-prompt coexist fine, so resumed sessions
|
|
269
|
+
// still pick up fresh system prompts and MCP tools (registered globally).
|
|
261
270
|
const SESSION_MAP_FILE = path.join(os.tmpdir(), `vo-sessions-${(argv.agent || 'pending').replace(/\./g, '-')}.json`)
|
|
262
271
|
const sessionMap = new Map()
|
|
263
272
|
|
|
264
|
-
//
|
|
265
|
-
// ignore new --append-system-prompt and MCP tools.
|
|
273
|
+
// Load persisted sessions from previous clock-in (if any)
|
|
266
274
|
try {
|
|
267
|
-
require('fs').
|
|
268
|
-
|
|
269
|
-
|
|
275
|
+
const raw = require('fs').readFileSync(SESSION_MAP_FILE, 'utf-8')
|
|
276
|
+
const parsed = JSON.parse(raw)
|
|
277
|
+
// Support both formats: Array of entries [[k,v], ...] and Object {k: v}
|
|
278
|
+
const entries = Array.isArray(parsed) ? parsed : Object.entries(parsed)
|
|
279
|
+
for (const [k, v] of entries) sessionMap.set(k, v)
|
|
280
|
+
log(chalk.dim(`Restored ${sessionMap.size} session mapping(s) from previous clock-in`))
|
|
281
|
+
} catch { /* no file or invalid — start fresh */ }
|
|
282
|
+
|
|
283
|
+
/** Persist session map to disk (fire-and-forget) */
|
|
284
|
+
function persistSessionMap() {
|
|
285
|
+
try {
|
|
286
|
+
require('fs').writeFileSync(SESSION_MAP_FILE, JSON.stringify([...sessionMap]), 'utf-8')
|
|
287
|
+
} catch { /* best-effort */ }
|
|
288
|
+
}
|
|
270
289
|
|
|
271
290
|
// Track active command processes PER SESSION for concurrent conversation support.
|
|
272
291
|
// Key: sessionId, Value: { child, commandId }. Different clients (web, Telegram)
|
|
@@ -516,7 +535,7 @@ async function handleMessage(message) {
|
|
|
516
535
|
// Kill previous command for THIS SESSION only. Other sessions continue in parallel.
|
|
517
536
|
const prev = sessionId ? activeChildren.get(sessionId) : null
|
|
518
537
|
if (prev?.child) {
|
|
519
|
-
log(chalk.dim(`[${
|
|
538
|
+
log(chalk.dim(`[${sessionId}] Killing previous command for same session`))
|
|
520
539
|
sendJSON({
|
|
521
540
|
type: 'streaming.aborted',
|
|
522
541
|
sessionId,
|
|
@@ -538,6 +557,9 @@ async function handleMessage(message) {
|
|
|
538
557
|
const claudeSessionId = sessionId ? sessionMap.get(sessionId) : null
|
|
539
558
|
if (claudeSessionId && providerConfig.resumeFlag) {
|
|
540
559
|
args.push(providerConfig.resumeFlag, claudeSessionId)
|
|
560
|
+
log(chalk.green(`[session] Resuming: ${sessionId} → ${claudeSessionId}`))
|
|
561
|
+
} else if (sessionId) {
|
|
562
|
+
log(chalk.yellow(`[session] No resume binding for ${sessionId} (map size: ${sessionMap.size})`))
|
|
541
563
|
}
|
|
542
564
|
// System prompt + platform context injection.
|
|
543
565
|
// shell: false — no escaping needed, args passed directly.
|
|
@@ -734,6 +756,11 @@ async function handleMessage(message) {
|
|
|
734
756
|
return
|
|
735
757
|
}
|
|
736
758
|
|
|
759
|
+
// Citations delta — ignore silently (citations are embedded in final text)
|
|
760
|
+
if (streamEvent?.type === 'content_block_delta' && streamEvent?.delta?.type === 'citations_delta') {
|
|
761
|
+
return
|
|
762
|
+
}
|
|
763
|
+
|
|
737
764
|
// Thinking deltas
|
|
738
765
|
if (streamEvent?.type === 'content_block_delta' && streamEvent?.delta?.type === 'thinking_delta') {
|
|
739
766
|
const deltaText = streamEvent.delta.thinking || streamEvent.delta.text || ''
|
|
@@ -788,19 +815,42 @@ async function handleMessage(message) {
|
|
|
788
815
|
return
|
|
789
816
|
}
|
|
790
817
|
|
|
791
|
-
|
|
792
|
-
|
|
818
|
+
// All tool-like block types: tool_use (client), server_tool_use (Anthropic server),
|
|
819
|
+
// mcp_tool_use, and result blocks that appear as content_block_start in stream-json.
|
|
820
|
+
// The original Claude CLI sets "tool-input" spinner state for all of these.
|
|
821
|
+
const TOOL_BLOCK_TYPES = new Set([
|
|
822
|
+
'tool_use', 'server_tool_use', 'mcp_tool_use',
|
|
823
|
+
'web_search_tool_result', 'web_fetch_tool_result',
|
|
824
|
+
'code_execution_tool_result', 'bash_code_execution_tool_result',
|
|
825
|
+
'text_editor_code_execution_tool_result', 'tool_search_tool_result',
|
|
826
|
+
'mcp_tool_result', 'container_upload',
|
|
827
|
+
])
|
|
828
|
+
if (TOOL_BLOCK_TYPES.has(block?.type)) {
|
|
829
|
+
// For server_tool_use, the tool name is in block.name (e.g. "web_search")
|
|
830
|
+
// For result blocks, derive name from block type itself
|
|
831
|
+
const rawName = block.name || block.type
|
|
832
|
+
const toolUseId = block.id || block.tool_use_id || `tool-${now}-${Math.random().toString(36).slice(2, 8)}`
|
|
793
833
|
if (blockIndex !== null) {
|
|
794
834
|
activeToolsByIndex.set(blockIndex, {
|
|
795
835
|
toolUseId,
|
|
796
|
-
toolName:
|
|
797
|
-
input: block.input,
|
|
836
|
+
toolName: rawName,
|
|
837
|
+
input: block.input || {},
|
|
798
838
|
})
|
|
799
839
|
}
|
|
800
840
|
emitToolStart({
|
|
801
841
|
toolUseId,
|
|
802
|
-
toolName:
|
|
803
|
-
input: block.input,
|
|
842
|
+
toolName: rawName,
|
|
843
|
+
input: block.input || {},
|
|
844
|
+
timestamp: now,
|
|
845
|
+
})
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
// Compaction events — notify frontend that context is being compressed
|
|
849
|
+
if (block?.type === 'compaction') {
|
|
850
|
+
emitToolStart({
|
|
851
|
+
toolUseId: block.id || `compaction-${now}`,
|
|
852
|
+
toolName: 'Compaction',
|
|
853
|
+
input: {},
|
|
804
854
|
timestamp: now,
|
|
805
855
|
})
|
|
806
856
|
}
|
|
@@ -865,10 +915,11 @@ async function handleMessage(message) {
|
|
|
865
915
|
if (block.type === 'text' && block.text) {
|
|
866
916
|
if (!fullText) fullText = block.text
|
|
867
917
|
}
|
|
868
|
-
// Extract complete
|
|
918
|
+
// Extract complete tool input — the assistant message contains the
|
|
869
919
|
// fully accumulated input (unlike content_block_start which has {}).
|
|
870
|
-
// This
|
|
871
|
-
|
|
920
|
+
// This applies to tool_use, server_tool_use, and mcp_tool_use blocks.
|
|
921
|
+
const isToolBlock = block.type === 'tool_use' || block.type === 'server_tool_use' || block.type === 'mcp_tool_use'
|
|
922
|
+
if (isToolBlock && block.id && block.input) {
|
|
872
923
|
const existing = activeToolsById.get(block.id)
|
|
873
924
|
if (existing && (!existing.input || Object.keys(existing.input).length === 0)) {
|
|
874
925
|
existing.input = block.input
|
|
@@ -921,6 +972,39 @@ async function handleMessage(message) {
|
|
|
921
972
|
}
|
|
922
973
|
return
|
|
923
974
|
}
|
|
975
|
+
|
|
976
|
+
// System events — forward plan mode, hooks, and other system subtypes
|
|
977
|
+
// These are top-level events (not wrapped in stream_event) that the CLI
|
|
978
|
+
// emits in --verbose stream-json mode for UI state changes.
|
|
979
|
+
if (event.type === 'system') {
|
|
980
|
+
const subtype = event.subtype
|
|
981
|
+
// Plan mode transitions — let frontend know agent entered/exited planning
|
|
982
|
+
if (subtype === 'plan_mode' || subtype === 'plan_mode_exit' || subtype === 'plan_mode_reentry') {
|
|
983
|
+
sendJSON({ type: 'system_event', sessionId, commandId, event: { subtype, timestamp: now } })
|
|
984
|
+
return
|
|
985
|
+
}
|
|
986
|
+
// Hook lifecycle — show user that hooks are running
|
|
987
|
+
if (subtype === 'hook_started' || subtype === 'hook_progress' || subtype === 'hook_response') {
|
|
988
|
+
sendJSON({ type: 'system_event', sessionId, commandId, event: { subtype, hookName: event.hook_name, timestamp: now } })
|
|
989
|
+
return
|
|
990
|
+
}
|
|
991
|
+
// Task/agent notifications (subagent spawned, progress, etc.)
|
|
992
|
+
if (subtype === 'task_notification' || subtype === 'task_progress') {
|
|
993
|
+
sendJSON({ type: 'system_event', sessionId, commandId, event: { subtype, message: event.message, timestamp: now } })
|
|
994
|
+
return
|
|
995
|
+
}
|
|
996
|
+
// MCP progress
|
|
997
|
+
if (subtype === 'mcp_message' || subtype === 'mcp_progress') {
|
|
998
|
+
sendJSON({ type: 'system_event', sessionId, commandId, event: { subtype, message: event.message, timestamp: now } })
|
|
999
|
+
return
|
|
1000
|
+
}
|
|
1001
|
+
// Context compaction boundaries
|
|
1002
|
+
if (subtype === 'compact_boundary' || subtype === 'microcompact_boundary') {
|
|
1003
|
+
sendJSON({ type: 'system_event', sessionId, commandId, event: { subtype, timestamp: now } })
|
|
1004
|
+
return
|
|
1005
|
+
}
|
|
1006
|
+
return
|
|
1007
|
+
}
|
|
924
1008
|
} catch {
|
|
925
1009
|
// Not JSON or unrecognized format — ignore
|
|
926
1010
|
}
|
|
@@ -947,6 +1031,7 @@ async function handleMessage(message) {
|
|
|
947
1031
|
// Store Claude session_id for conversation continuity
|
|
948
1032
|
if (resultSessionId && sessionId) {
|
|
949
1033
|
sessionMap.set(sessionId, resultSessionId)
|
|
1034
|
+
persistSessionMap()
|
|
950
1035
|
log(chalk.dim(`Session mapped: ${sessionId} → ${resultSessionId}`))
|
|
951
1036
|
}
|
|
952
1037
|
|
|
@@ -1132,18 +1217,14 @@ function connect() {
|
|
|
1132
1217
|
if (mcpRegistered) mcpConfigPath = 'registered' // flag to skip re-registration
|
|
1133
1218
|
}
|
|
1134
1219
|
|
|
1135
|
-
// Banner
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
console.log(chalk.dim(` Identity: ${cachedSystemPrompt ? 'loaded' : 'default'}`))
|
|
1144
|
-
console.log(chalk.dim(` Tools: ${mcpRegistered ? 'VO MCP registered ✓' : 'basic only'}`))
|
|
1145
|
-
console.log(chalk.dim(` Press Ctrl+C to clock out`))
|
|
1146
|
-
console.log('')
|
|
1220
|
+
// Banner — use the printClockInBanner from onboarding for consistent look
|
|
1221
|
+
const { printClockInBanner: printBanner } = await import('./onboarding.js')
|
|
1222
|
+
printBanner({
|
|
1223
|
+
agentHandle: argv.agent,
|
|
1224
|
+
model: argv.provider,
|
|
1225
|
+
seat: null,
|
|
1226
|
+
workspace,
|
|
1227
|
+
})
|
|
1147
1228
|
})
|
|
1148
1229
|
|
|
1149
1230
|
ws.on('message', (data) => {
|
package/onboarding.js
CHANGED
|
@@ -125,38 +125,67 @@ export async function checkForUpdate() {
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
// ── Adam Sprite Art (auto-generated from pixel art sprite sheet) ──────────
|
|
129
|
+
// Half-block rendering: 2x2 pixels → 1 char, truecolor ANSI
|
|
130
|
+
import { ADAM_IDLE_FRAMES, ADAM_SIT_FRAME, ADAM_FRAMES } from './adam-frames.js'
|
|
131
|
+
|
|
128
132
|
// ── Banner ────────────────────────────────────────────────────────────────
|
|
129
133
|
|
|
130
134
|
export function printBanner(subtitle = 'Manage Your AI Agents') {
|
|
135
|
+
const frame = ADAM_IDLE_FRAMES[0]
|
|
136
|
+
|
|
137
|
+
// Text lines aligned to Adam sprite rows (~23 rows)
|
|
138
|
+
// Adam is ~32 chars wide; text appears to the right with padding
|
|
139
|
+
const textLines = [
|
|
140
|
+
'',
|
|
141
|
+
'',
|
|
142
|
+
'',
|
|
143
|
+
'',
|
|
144
|
+
'',
|
|
145
|
+
'',
|
|
146
|
+
'',
|
|
147
|
+
'',
|
|
148
|
+
chalk.bold.white(' Virtual Office'),
|
|
149
|
+
chalk.dim(` ${subtitle}`),
|
|
150
|
+
'',
|
|
151
|
+
chalk.dim(' office.xyz'),
|
|
152
|
+
]
|
|
153
|
+
|
|
131
154
|
console.log('')
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
console.log(chalk.cyan(' │') + chalk.bold.white(' ▓░░▓') + chalk.bold.white(' Virtual Office') + ' ' + chalk.cyan('│'))
|
|
136
|
-
console.log(chalk.cyan(' │') + chalk.bold.white(' ▓▓▓▓') + chalk.dim(` ${subtitle}`) + ' ' + chalk.cyan('│'))
|
|
137
|
-
console.log(chalk.cyan(' │') + chalk.bold.white(' ██') + ' ' + chalk.cyan('│'))
|
|
138
|
-
console.log(chalk.cyan(' │') + chalk.dim(' ▓▓▓▓ office.xyz') + ' ' + chalk.cyan('│'))
|
|
139
|
-
console.log(chalk.cyan(' │') + ' ' + chalk.cyan('│'))
|
|
140
|
-
console.log(chalk.cyan(' └─────────────────────────────────────────┘'))
|
|
155
|
+
for (let i = 0; i < frame.length; i++) {
|
|
156
|
+
console.log(' ' + frame[i] + (textLines[i] || ''))
|
|
157
|
+
}
|
|
141
158
|
console.log('')
|
|
142
159
|
}
|
|
143
160
|
|
|
144
161
|
export function printClockInBanner({ agentHandle, model, seat, workspace }) {
|
|
162
|
+
const frame = ADAM_SIT_FRAME
|
|
163
|
+
|
|
164
|
+
// Right-side info lines aligned to Adam's sitting sprite (~23 rows)
|
|
165
|
+
const infoLines = [
|
|
166
|
+
'',
|
|
167
|
+
'',
|
|
168
|
+
'',
|
|
169
|
+
'',
|
|
170
|
+
'',
|
|
171
|
+
chalk.green.bold(' ✓ Clocked in to Virtual Office'),
|
|
172
|
+
'',
|
|
173
|
+
chalk.dim(' Agent: ') + chalk.bold.white(agentHandle),
|
|
174
|
+
chalk.dim(' Model: ') + chalk.white(model || 'Claude Opus 4.6'),
|
|
175
|
+
seat ? (chalk.dim(' Seat: ') + chalk.white(seat)) : '',
|
|
176
|
+
chalk.dim(' Dir: ') + chalk.white(workspace || process.cwd()),
|
|
177
|
+
'',
|
|
178
|
+
chalk.dim(' Web: ') + chalk.underline.cyan('https://beta.office.xyz'),
|
|
179
|
+
'',
|
|
180
|
+
chalk.dim(' Press ') + chalk.yellow('Ctrl+C') + chalk.dim(' to clock out'),
|
|
181
|
+
]
|
|
182
|
+
|
|
145
183
|
console.log('')
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
console.log(chalk.cyan(' │') + chalk.dim(' Model: ') + chalk.white(model || 'Claude Opus 4.6'))
|
|
151
|
-
if (seat) {
|
|
152
|
-
console.log(chalk.cyan(' │') + chalk.dim(' Seat: ') + chalk.white(seat))
|
|
184
|
+
for (let i = 0; i < Math.max(frame.length, infoLines.length); i++) {
|
|
185
|
+
const sprite = (i < frame.length) ? frame[i] : ''
|
|
186
|
+
const info = (i < infoLines.length) ? infoLines[i] : ''
|
|
187
|
+
console.log(' ' + sprite + info)
|
|
153
188
|
}
|
|
154
|
-
console.log(chalk.cyan(' │') + chalk.dim(' Workspace: ') + chalk.white(workspace || process.cwd()))
|
|
155
|
-
console.log(chalk.cyan(' │') + ' ' + chalk.cyan('│'))
|
|
156
|
-
console.log(chalk.cyan(' │') + chalk.dim(' Web UI: ') + chalk.underline.cyan('https://beta.office.xyz'))
|
|
157
|
-
console.log(chalk.cyan(' │') + ' ' + chalk.cyan('│'))
|
|
158
|
-
console.log(chalk.cyan(' │') + chalk.yellow(' Press Ctrl+C to clock out') + ' ' + chalk.cyan('│'))
|
|
159
|
-
console.log(chalk.cyan(' └─────────────────────────────────────────┘'))
|
|
160
189
|
console.log('')
|
|
161
190
|
}
|
|
162
191
|
|
|
@@ -510,19 +539,54 @@ export async function runOnboarding() {
|
|
|
510
539
|
// 1. Check cached session
|
|
511
540
|
const cached = loadSession()
|
|
512
541
|
|
|
513
|
-
if (cached?.sessionToken && cached?.lastAgent?.handle
|
|
514
|
-
// Quick reconnect path
|
|
542
|
+
if (cached?.sessionToken && cached?.lastAgent?.handle) {
|
|
543
|
+
// Quick reconnect path — always refresh token to avoid stale token rejection
|
|
515
544
|
const spinner = ora('Validating session...').start()
|
|
516
545
|
try {
|
|
517
546
|
const validation = await api('GET', '/api/cli/auth/session', null, cached.sessionToken)
|
|
518
547
|
if (validation.success) {
|
|
548
|
+
spinner.text = 'Refreshing connection...'
|
|
549
|
+
|
|
550
|
+
// Re-hire with same agent name to get a fresh connectionToken.
|
|
551
|
+
// This is idempotent — if agent exists, it just refreshes the token.
|
|
552
|
+
const agentName = cached.lastAgent.handle.split('.')[0]
|
|
553
|
+
const officeId = cached.lastOfficeId || null
|
|
554
|
+
|
|
555
|
+
let freshToken = cached.lastAgent.connectionToken
|
|
556
|
+
let seat = cached.lastAgent.seat
|
|
557
|
+
|
|
558
|
+
if (agentName && officeId) {
|
|
559
|
+
try {
|
|
560
|
+
const result = await api('POST', '/api/cli/office/hire', {
|
|
561
|
+
officeId,
|
|
562
|
+
agentName,
|
|
563
|
+
provider: 'claude-code',
|
|
564
|
+
}, cached.sessionToken)
|
|
565
|
+
freshToken = result.connectionToken || freshToken
|
|
566
|
+
seat = result.seat || seat
|
|
567
|
+
|
|
568
|
+
// Update cached session with fresh token
|
|
569
|
+
saveSession({
|
|
570
|
+
...cached,
|
|
571
|
+
lastAgent: {
|
|
572
|
+
...cached.lastAgent,
|
|
573
|
+
connectionToken: freshToken,
|
|
574
|
+
seat,
|
|
575
|
+
},
|
|
576
|
+
})
|
|
577
|
+
} catch (err) {
|
|
578
|
+
// If refresh fails, try with cached token anyway
|
|
579
|
+
console.log(chalk.dim(` Token refresh failed (${err.message}), using cached token`))
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
519
583
|
spinner.succeed(`Welcome back, ${chalk.bold(cached.email || cached.displayName || 'user')}!`)
|
|
520
584
|
console.log(chalk.dim(` Reconnecting as ${cached.lastAgent.handle}...`))
|
|
521
585
|
console.log(chalk.dim(` Web interface: ${chalk.underline.cyan('https://beta.office.xyz')}`))
|
|
522
586
|
return {
|
|
523
587
|
agent: cached.lastAgent.handle,
|
|
524
|
-
token:
|
|
525
|
-
seat
|
|
588
|
+
token: freshToken,
|
|
589
|
+
seat,
|
|
526
590
|
}
|
|
527
591
|
}
|
|
528
592
|
} catch {
|
|
@@ -548,6 +612,7 @@ export async function runOnboarding() {
|
|
|
548
612
|
sessionToken: session.sessionToken,
|
|
549
613
|
expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
|
|
550
614
|
lastOffice: domain,
|
|
615
|
+
lastOfficeId: officeId,
|
|
551
616
|
lastAgent: {
|
|
552
617
|
handle: hired.agentHandle,
|
|
553
618
|
connectionToken: hired.connectionToken,
|