@miphamai/cli 0.4.0 → 0.5.1

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 (60) hide show
  1. package/bin/mipham +51 -24
  2. package/bin/mipham.ts +186 -0
  3. package/package.json +1 -1
  4. package/src/agent/agent-context.ts +56 -0
  5. package/src/agent/agent-registry.ts +134 -0
  6. package/src/agent/sub-agent.ts +73 -88
  7. package/src/agent/types.ts +39 -0
  8. package/src/agent-view/agent-view-manager.ts +217 -0
  9. package/src/agent-view/dashboard.tsx +218 -0
  10. package/src/agent-view/session-peek.tsx +72 -0
  11. package/src/agent-view/session-row.tsx +70 -0
  12. package/src/artifacts/server.ts +31 -0
  13. package/src/artifacts/versioning.ts +127 -0
  14. package/src/config/defaults.ts +2 -1
  15. package/src/core/context-compact.ts +50 -0
  16. package/src/core/context-drain.ts +54 -0
  17. package/src/core/context-microcompact.ts +132 -0
  18. package/src/core/context-snip.ts +74 -0
  19. package/src/core/context-token.ts +108 -0
  20. package/src/core/context.ts +169 -0
  21. package/src/core/engine.ts +144 -3
  22. package/src/core/hooks-config.ts +52 -0
  23. package/src/core/hooks-executor.ts +113 -0
  24. package/src/core/hooks.ts +90 -30
  25. package/src/core/instructions.ts +11 -0
  26. package/src/core/memory/memory-loader.ts +23 -0
  27. package/src/core/memory/memory-manager.ts +192 -0
  28. package/src/core/memory/memory-writer.ts +72 -0
  29. package/src/core/permission-config.ts +34 -0
  30. package/src/core/permission-rules.ts +66 -0
  31. package/src/core/permission.ts +221 -36
  32. package/src/index.tsx +20 -0
  33. package/src/plugin/plugin-loader.ts +36 -0
  34. package/src/plugin/plugin-manager.ts +125 -0
  35. package/src/plugin/plugin-validator.ts +41 -0
  36. package/src/shared/types.ts +61 -1
  37. package/src/skills/fork-executor.ts +40 -0
  38. package/src/skills/loader.ts +46 -0
  39. package/src/tools/agent/agent.ts +20 -11
  40. package/src/tools/agent/skill.ts +32 -2
  41. package/src/tools/computer/app-launcher.ts +39 -0
  42. package/src/tools/computer/browser.ts +48 -0
  43. package/src/tools/computer/computer-use.ts +88 -0
  44. package/src/tools/computer/playwright.d.ts +7 -0
  45. package/src/tools/computer/screenshot.ts +57 -0
  46. package/src/tools/index.ts +3 -0
  47. package/src/tools/network/web-fetch.ts +1 -1
  48. package/src/ui/app.tsx +14 -3
  49. package/src/ui/commands.ts +101 -32
  50. package/src/ui/input.tsx +123 -8
  51. package/src/ui/vim-motions.ts +96 -0
  52. package/src/workflow/budget.ts +33 -0
  53. package/src/workflow/journal.ts +105 -0
  54. package/src/workflow/primitives/agent.ts +51 -0
  55. package/src/workflow/primitives/parallel.ts +8 -0
  56. package/src/workflow/primitives/phase.ts +19 -0
  57. package/src/workflow/primitives/pipeline.ts +24 -0
  58. package/src/workflow/runtime.ts +87 -0
  59. package/src/workflow/sandbox.ts +79 -0
  60. package/dist/mipham +0 -0
@@ -1,5 +1,6 @@
1
1
  import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs'
2
2
  import { join } from 'node:path'
3
+ import { homedir } from 'node:os'
3
4
  import { parse as parseYaml } from 'yaml'
4
5
  import type { SkillDefinition } from '../shared/index.ts'
5
6
 
@@ -94,6 +95,12 @@ export class SkillsLoader {
94
95
  tools: data.tools as SkillDefinition['tools'],
95
96
  hooks: data.hooks as SkillDefinition['hooks'],
96
97
  prompts: data.prompts as SkillDefinition['prompts'],
98
+ // NEW: frontmatter fields for fork/auto-trigger support
99
+ context: data.context as string | undefined,
100
+ model: data.model as string | undefined,
101
+ allowedTools: data['allowed-tools'] as string[] | undefined,
102
+ disableModelInvocation: data['disable-model-invocation'] as boolean | undefined,
103
+ userInvocable: data['user-invocable'] as boolean | undefined,
97
104
  }
98
105
 
99
106
  this.skills.set(skill.name, skill)
@@ -102,6 +109,45 @@ export class SkillsLoader {
102
109
  }
103
110
  }
104
111
 
112
+ /**
113
+ * Build the system-reminder block for AI auto-triggering.
114
+ * Injects available skill names + descriptions so the AI can match
115
+ * user requests to relevant skills.
116
+ */
117
+ buildSystemReminder(maxTokens: number = 5000): string {
118
+ const skills = this.list().filter((s) => {
119
+ // Skip skills that disable model invocation
120
+ return !s.disableModelInvocation
121
+ })
122
+
123
+ if (skills.length === 0) return ''
124
+
125
+ const lines: string[] = [
126
+ '<system-reminder>',
127
+ 'The following skills are available. Invoke via the Skill tool when relevant:',
128
+ ]
129
+
130
+ let tokenBudget = 0
131
+ for (const skill of skills) {
132
+ const entry = `- ${skill.name}: ${skill.description}`
133
+ const entryTokens = Math.ceil(entry.length / 4) + 1 // rough estimate
134
+ if (tokenBudget + entryTokens > maxTokens) break
135
+
136
+ lines.push(entry)
137
+ tokenBudget += entryTokens
138
+ }
139
+
140
+ lines.push('</system-reminder>')
141
+ return lines.join('\n')
142
+ }
143
+
144
+ /** Load skills from ~/.mipham/skills/ (user home directory). */
145
+ loadUserSkills(): void {
146
+ const home = homedir()
147
+ const userSkillsPath = join(home, '.mipham', 'skills')
148
+ this.loadExternal([userSkillsPath])
149
+ }
150
+
105
151
  private nameFromPath(path: string): string {
106
152
  const base = path.split('/').pop() || ''
107
153
  return base.replace(/\.(SKILL|mipham-skill)\.md$/i, '')
@@ -1,5 +1,6 @@
1
1
  import type { ToolDefinition } from '../../shared/index.ts'
2
- import { SubAgent, type SubAgentType } from '../../agent/sub-agent'
2
+ import { SubAgent } from '../../agent/sub-agent'
3
+ import type { SubAgentType } from '../../agent/types'
3
4
 
4
5
  const VALID_TYPES: SubAgentType[] = ['general', 'explore', 'plan', 'code-review']
5
6
 
@@ -35,17 +36,25 @@ export const agentTool: ToolDefinition = {
35
36
  }
36
37
 
37
38
  const registry = ctx.registry
38
- if (!registry) {
39
- // No registry in context — return structured simulation
40
- const sub = new SubAgent(
41
- null as unknown as import('../../providers/registry').ProviderRegistry,
42
- )
43
- const result = await sub.execute(prompt, description, { type: agentType })
44
- return { success: true, content: result }
39
+ const toolRegistry = ctx.toolRegistry
40
+ if (!registry || !toolRegistry) {
41
+ return {
42
+ success: false,
43
+ content: '',
44
+ error:
45
+ 'Sub-agent execution requires an active provider and tool registry. Connect a provider API key first.',
46
+ }
45
47
  }
46
48
 
47
- const sub = new SubAgent(registry)
48
- const result = await sub.execute(prompt, description, { type: agentType })
49
- return { success: true, content: result }
49
+ // Resolve agent definition from registry (custom > builtin)
50
+ const agentDef = ctx.agentRegistry?.resolve(agentType)
51
+
52
+ try {
53
+ const sub = new SubAgent(registry, toolRegistry)
54
+ const result = await sub.execute(prompt, description, { type: agentType, agentDef })
55
+ return { success: true, content: result }
56
+ } catch (err) {
57
+ return { success: false, content: '', error: String(err) }
58
+ }
50
59
  },
51
60
  }
@@ -1,4 +1,5 @@
1
- import type { ToolDefinition } from '../../shared/index.ts'
1
+ import type { ToolDefinition, SkillDefinition, ToolResult } from '../../shared/index.ts'
2
+ import { executeForkedSkill } from '../../skills/fork-executor'
2
3
 
3
4
  export const skillTool: ToolDefinition = {
4
5
  name: 'Skill',
@@ -34,7 +35,36 @@ export const skillTool: ToolDefinition = {
34
35
  }
35
36
  }
36
37
 
37
- // Build skill invocation content
38
+ // Check if skill has context: fork — execute in isolated subagent
39
+ if (skill.context === 'fork') {
40
+ const registry = ctx.registry
41
+ if (!registry) {
42
+ return {
43
+ success: false,
44
+ content: '',
45
+ error: 'Provider registry not available for forked skill execution.',
46
+ }
47
+ }
48
+
49
+ try {
50
+ const result = await executeForkedSkill(
51
+ skill,
52
+ args,
53
+ registry,
54
+ ctx.toolRegistry || new Map(),
55
+ )
56
+ // Return to AI as internal context
57
+ return { success: true, content: `[Forked skill "${skillName}" result]:\n${result}` }
58
+ } catch (err) {
59
+ return {
60
+ success: false,
61
+ content: '',
62
+ error: `Forked skill execution failed: ${String(err)}`,
63
+ }
64
+ }
65
+ }
66
+
67
+ // Standard inline execution — return metadata for AI to follow
38
68
  const lines: string[] = [
39
69
  `── Skill Invoked: ${skill.name} ──`,
40
70
  `Type: ${skill.type} | Version: ${skill.version}`,
@@ -0,0 +1,39 @@
1
+ import { execSync } from 'node:child_process'
2
+
3
+ const ALLOWED_APPS = new Set([
4
+ 'Finder',
5
+ 'Safari',
6
+ 'Google Chrome',
7
+ 'Firefox',
8
+ 'Terminal',
9
+ 'VS Code',
10
+ 'System Settings',
11
+ 'Activity Monitor',
12
+ 'TextEdit',
13
+ 'Preview',
14
+ ])
15
+
16
+ export function launchApp(appName: string): { success: boolean; message: string } {
17
+ if (!ALLOWED_APPS.has(appName)) {
18
+ return {
19
+ success: false,
20
+ message: `App "${appName}" is not in the allowed list. Allowed: ${[...ALLOWED_APPS].join(', ')}`,
21
+ }
22
+ }
23
+
24
+ try {
25
+ const platform = process.platform
26
+ if (platform === 'darwin') {
27
+ execSync(`open -a "${appName}"`, { timeout: 5000 })
28
+ } else if (platform === 'linux') {
29
+ execSync(`xdg-open "${appName}"`, { timeout: 5000 })
30
+ } else if (platform === 'win32') {
31
+ execSync(`start "" "${appName}"`, { timeout: 5000, shell: 'cmd.exe' })
32
+ } else {
33
+ return { success: false, message: `Unsupported platform: ${platform}` }
34
+ }
35
+ return { success: true, message: `Launched: ${appName}` }
36
+ } catch (err) {
37
+ return { success: false, message: `Failed to launch ${appName}: ${String(err)}` }
38
+ }
39
+ }
@@ -0,0 +1,48 @@
1
+ let browserPromise: Promise<unknown> | null = null
2
+
3
+ async function getPage(): Promise<unknown> {
4
+ if (!browserPromise) {
5
+ // Dynamic import — Playwright is optional
6
+ let playwrightModule: { chromium: unknown }
7
+ try {
8
+ playwrightModule = await import('playwright')
9
+ } catch {
10
+ throw new Error('Playwright is not installed. Install it with: npm install playwright')
11
+ }
12
+ const { chromium } = playwrightModule as {
13
+ chromium: {
14
+ launch: (opts?: Record<string, unknown>) => Promise<{
15
+ newPage: () => Promise<unknown>
16
+ }>
17
+ }
18
+ }
19
+ const browser = await chromium.launch({ headless: false })
20
+ browserPromise = browser.newPage()
21
+ }
22
+ return browserPromise
23
+ }
24
+
25
+ export async function browserNavigate(url: string): Promise<string> {
26
+ const page = (await getPage()) as {
27
+ goto: (u: string) => Promise<void>
28
+ url: () => string
29
+ }
30
+ await page.goto(url)
31
+ return `Navigated to: ${page.url()}`
32
+ }
33
+
34
+ export async function browserSnapshot(): Promise<string> {
35
+ const page = (await getPage()) as {
36
+ accessibility: { snapshot: () => Promise<unknown> }
37
+ }
38
+ const snapshot = await page.accessibility.snapshot()
39
+ return JSON.stringify(snapshot, null, 2)
40
+ }
41
+
42
+ export async function browserClick(uid: string): Promise<string> {
43
+ const page = (await getPage()) as {
44
+ locator: (s: string) => { click: () => Promise<void> }
45
+ }
46
+ await page.locator(`[uid="${uid}"]`).click()
47
+ return `Clicked: ${uid}`
48
+ }
@@ -0,0 +1,88 @@
1
+ import type { ToolDefinition } from '../../shared/index.ts'
2
+ import { takeScreenshot } from './screenshot'
3
+ import { launchApp } from './app-launcher'
4
+ import { browserNavigate, browserSnapshot, browserClick } from './browser'
5
+
6
+ export const computerUseTool: ToolDefinition = {
7
+ name: 'ComputerUse',
8
+ description:
9
+ 'Take screenshots, launch apps, or control a browser. Always requires user approval.',
10
+ category: 'system',
11
+ permission: 'ask', // ALWAYS ask — security requirement
12
+ parameters: {
13
+ type: 'object',
14
+ properties: {
15
+ action: {
16
+ type: 'string',
17
+ enum: ['screenshot', 'launch', 'browser_navigate', 'browser_snapshot', 'browser_click'],
18
+ description: 'The computer-use action to perform',
19
+ },
20
+ target: {
21
+ type: 'string',
22
+ description: 'App name (launch), URL (browser_navigate), or element UID (browser_click)',
23
+ },
24
+ text: {
25
+ type: 'string',
26
+ description: 'Text to type (reserved for future browser_type action)',
27
+ },
28
+ },
29
+ required: ['action'],
30
+ },
31
+ async execute(params, _ctx) {
32
+ const action = params.action as string
33
+ const target = (params.target as string) || ''
34
+
35
+ switch (action) {
36
+ case 'screenshot': {
37
+ const result = await takeScreenshot()
38
+ return result.success
39
+ ? { success: true, content: `Screenshot captured: ${result.data!.slice(0, 100)}...` }
40
+ : { success: false, content: '', error: result.error }
41
+ }
42
+ case 'launch': {
43
+ const result = launchApp(target)
44
+ return result.success
45
+ ? { success: true, content: result.message }
46
+ : { success: false, content: '', error: result.message }
47
+ }
48
+ case 'browser_navigate': {
49
+ try {
50
+ const url = await browserNavigate(target)
51
+ return { success: true, content: url }
52
+ } catch (err) {
53
+ return {
54
+ success: false,
55
+ content: '',
56
+ error: `Browser navigation failed: ${String(err)}`,
57
+ }
58
+ }
59
+ }
60
+ case 'browser_snapshot': {
61
+ try {
62
+ const snapshot = await browserSnapshot()
63
+ return { success: true, content: snapshot }
64
+ } catch (err) {
65
+ return {
66
+ success: false,
67
+ content: '',
68
+ error: `Browser snapshot failed: ${String(err)}`,
69
+ }
70
+ }
71
+ }
72
+ case 'browser_click': {
73
+ try {
74
+ const result = await browserClick(target)
75
+ return { success: true, content: result }
76
+ } catch (err) {
77
+ return {
78
+ success: false,
79
+ content: '',
80
+ error: `Browser click failed: ${String(err)}`,
81
+ }
82
+ }
83
+ }
84
+ default:
85
+ return { success: false, content: '', error: `Unknown action: ${action}` }
86
+ }
87
+ },
88
+ }
@@ -0,0 +1,7 @@
1
+ declare module 'playwright' {
2
+ export const chromium: {
3
+ launch: (opts?: Record<string, unknown>) => Promise<{
4
+ newPage: () => Promise<unknown>
5
+ }>
6
+ }
7
+ }
@@ -0,0 +1,57 @@
1
+ import { execSync } from 'node:child_process'
2
+ import { readFileSync, unlinkSync, writeFileSync } from 'node:fs'
3
+ import { tmpdir } from 'node:os'
4
+ import { join } from 'node:path'
5
+
6
+ export async function takeScreenshot(): Promise<{
7
+ success: boolean
8
+ data?: string
9
+ error?: string
10
+ }> {
11
+ const tmpPath = join(tmpdir(), `mipham-screenshot-${Date.now()}.png`)
12
+
13
+ try {
14
+ const platform = process.platform
15
+ if (platform === 'darwin') {
16
+ execSync(`screencapture -x ${tmpPath}`, { timeout: 10000 })
17
+ } else if (platform === 'linux') {
18
+ execSync(`import -window root ${tmpPath}`, { timeout: 10000 })
19
+ } else if (platform === 'win32') {
20
+ // PowerShell script to capture primary screen and save as PNG
21
+ const psScript = `
22
+ Add-Type -AssemblyName System.Windows.Forms
23
+ Add-Type -AssemblyName System.Drawing
24
+ $screen = [System.Windows.Forms.Screen]::PrimaryScreen
25
+ $bitmap = New-Object System.Drawing.Bitmap($screen.Bounds.Width, $screen.Bounds.Height)
26
+ $graphics = [System.Drawing.Graphics]::FromImage($bitmap)
27
+ $graphics.CopyFromScreen($screen.Bounds.X, $screen.Bounds.Y, 0, 0, $bitmap.Size)
28
+ $bitmap.Save('${tmpPath.replace(/\\/g, '\\\\')}', [System.Drawing.Imaging.ImageFormat]::Png)
29
+ $graphics.Dispose()
30
+ $bitmap.Dispose()
31
+ `.trim()
32
+ const psPath = join(tmpdir(), `mipham-sc-${Date.now()}.ps1`)
33
+ writeFileSync(psPath, psScript, 'utf-8')
34
+ execSync(`powershell -ExecutionPolicy Bypass -File "${psPath}"`, { timeout: 15000 })
35
+ try {
36
+ unlinkSync(psPath)
37
+ } catch {
38
+ // ignore cleanup failures
39
+ }
40
+ }
41
+
42
+ const data = readFileSync(tmpPath, 'base64')
43
+ try {
44
+ unlinkSync(tmpPath)
45
+ } catch {
46
+ // ignore cleanup failures
47
+ }
48
+ return { success: true, data: `data:image/png;base64,${data}` }
49
+ } catch (err) {
50
+ try {
51
+ unlinkSync(tmpPath)
52
+ } catch {
53
+ // ignore cleanup failures
54
+ }
55
+ return { success: false, error: `Screenshot failed: ${String(err)}` }
56
+ }
57
+ }
@@ -16,6 +16,7 @@ import { webSearchTool } from './network/web-search'
16
16
  import { configTool } from './system/config'
17
17
  import { mcpTool } from './system/mcp'
18
18
  import { artifactTool } from './artifact/artifact'
19
+ import { computerUseTool } from './computer/computer-use'
19
20
 
20
21
  /**
21
22
  * Validate tool parameters against the tool's JSON Schema definition.
@@ -119,6 +120,8 @@ export function createToolRegistry(): Map<string, ToolDefinition> {
119
120
  withValidation(mcpTool),
120
121
  // Artifact tools
121
122
  withValidation(artifactTool),
123
+ // Computer Use tools
124
+ withValidation(computerUseTool),
122
125
  ]
123
126
 
124
127
  const map = new Map<string, ToolDefinition>()
@@ -28,7 +28,7 @@ export const webFetchTool: ToolDefinition = {
28
28
  const timer = setTimeout(() => controller.abort(), 30_000) // 30s timeout
29
29
 
30
30
  const response = await fetch(url, {
31
- headers: { 'User-Agent': 'Mipham-Code/0.1.0' },
31
+ headers: { 'User-Agent': 'Mipham-Code/0.5.0' },
32
32
  redirect: 'follow',
33
33
  signal: controller.signal,
34
34
  })
package/src/ui/app.tsx CHANGED
@@ -1,8 +1,9 @@
1
- import React, { useState, useCallback, useRef } from 'react'
1
+ import React, { useState, useCallback, useRef, useMemo } from 'react'
2
2
  import { Box, Text, useInput } from 'ink'
3
3
  import type { QueryEngine } from '../core/engine'
4
4
  import type { MiphamConfig } from '../shared/index.ts'
5
5
  import type { SkillsLoader } from '../skills/loader'
6
+ import { AgentRegistry } from '../agent/agent-registry'
6
7
  import { ChatPanel } from './chat'
7
8
  import { InputBar } from './input'
8
9
  import { ModelPicker } from './picker'
@@ -42,7 +43,9 @@ interface AgentProgress {
42
43
  startTime: number
43
44
  }
44
45
 
45
- const VERSION = '0.3.0'
46
+ import { PACKAGE_VERSION } from '@mipham/shared'
47
+
48
+ const VERSION = PACKAGE_VERSION
46
49
 
47
50
  type PermissionMode = 'auto' | 'ask' | 'bypass'
48
51
 
@@ -88,6 +91,14 @@ export function App({
88
91
  return () => clearInterval(interval)
89
92
  }, [agentProgress])
90
93
 
94
+ // Initialize agent registry (one-time, on mount)
95
+ useMemo(() => {
96
+ const agentRegistry = new AgentRegistry()
97
+ agentRegistry.loadUserAgents()
98
+ agentRegistry.loadProjectAgents(process.cwd())
99
+ engine.setAgentRegistry(agentRegistry)
100
+ }, [])
101
+
91
102
  const mkCtx = useCallback(
92
103
  (): CommandContext => ({
93
104
  engine,
@@ -348,7 +359,7 @@ export function App({
348
359
  <Text bold color="cyan">
349
360
  Mipham Code
350
361
  </Text>
351
- <Text dimColor> v0.3.0</Text>
362
+ <Text dimColor> v{VERSION}</Text>
352
363
  {sessionTitle ? (
353
364
  <Text color="yellow"> — {sessionTitle}</Text>
354
365
  ) : (
@@ -8,7 +8,7 @@ import type { QueryEngine } from '../core/engine'
8
8
  import type { MiphamConfig } from '../shared/index.ts'
9
9
  import type { SkillsLoader } from '../skills/loader'
10
10
  import { McpClient } from '../mcp/client'
11
- import { NPM_INSTALL_COMMAND, NPM_UPDATE_COMMAND } from '@mipham/shared'
11
+ import { NPM_INSTALL_COMMAND, NPM_UPDATE_COMMAND, PACKAGE_VERSION } from '@mipham/shared'
12
12
 
13
13
  export interface CommandContext {
14
14
  engine: QueryEngine
@@ -51,7 +51,7 @@ type CommandHandler = (
51
51
 
52
52
  const helpCmd: CommandHandler = (_ctx) => ({
53
53
  content: stripIndent`
54
- Mipham Code v0.2.0 — Commands
54
+ Mipham Code v${PACKAGE_VERSION} — Commands
55
55
 
56
56
  ── Session ──────────────────────────
57
57
  /help Show this help
@@ -126,7 +126,10 @@ const helpCmd: CommandHandler = (_ctx) => ({
126
126
  /login Show API key status
127
127
  /logout Clear credentials guide
128
128
  /feedback Send feedback
129
- /agents Agent management
129
+
130
+ ── Agents ──────────────────────────
131
+ /agents Agent view dashboard
132
+ /bg <prompt> Run a background agent task
130
133
 
131
134
  Type /exit or Esc to quit.
132
135
  `,
@@ -419,6 +422,7 @@ const goalCmd: CommandHandler = (ctx, args) => {
419
422
  }
420
423
  }
421
424
  ctx.setGoal(goal.trim())
425
+ ctx.engine.setGoal(goal.trim())
422
426
  return {
423
427
  content: `✓ Goal set: "${goal.trim()}"\n\nUse /status to view progress. Type /goal without arguments to clear.`,
424
428
  }
@@ -2078,47 +2082,111 @@ const feedbackCmd: CommandHandler = (ctx, args) => {
2078
2082
  // Phase 4 — Agent Management
2079
2083
  // ═══════════════════════════════════════════════════════════════
2080
2084
 
2081
- const agentsCmd: CommandHandler = () => ({
2082
- content: `── Agent System ──
2085
+ const agentsCmd: CommandHandler = (ctx) => {
2086
+ const agentViewManager = ctx.engine.getAgentViewManager?.()
2087
+ if (!agentViewManager) {
2088
+ return { content: 'Agent View dashboard is initializing...' }
2089
+ }
2090
+ const counts = agentViewManager.countByStatus()
2091
+ const total = counts['needs-input'] + counts.working + counts.completed + counts.failed
2092
+
2093
+ if (total === 0) {
2094
+ return {
2095
+ content: `── Agent View ──
2096
+
2097
+ No background agents running.
2098
+
2099
+ Use /bg <prompt> to spawn a background agent session.
2100
+
2101
+ Example:
2102
+ /bg Fix all TypeScript errors in the project
2103
+ /bg Review the auth module for security issues
2104
+
2105
+ Or use the Agent tool in a conversation to launch a sub-agent.`,
2106
+ }
2107
+ }
2108
+
2109
+ const lines: string[] = [
2110
+ '── Agent View ──',
2111
+ '',
2112
+ ` ${total} session(s):`,
2113
+ ` 🟡 Needs input: ${counts['needs-input']}`,
2114
+ ` 🔵 Working: ${counts.working}`,
2115
+ ` 🟢 Completed: ${counts.completed}`,
2116
+ ` 🔴 Failed: ${counts.failed}`,
2117
+ '',
2118
+ 'Sessions:',
2119
+ ]
2120
+
2121
+ const all = agentViewManager.list()
2122
+ for (const s of all.slice(0, 10)) {
2123
+ const statusIcon: Record<string, string> = {
2124
+ 'needs-input': '🟡',
2125
+ working: '🔵',
2126
+ completed: '🟢',
2127
+ failed: '🔴',
2128
+ }
2129
+ const elapsed =
2130
+ s.elapsedMs < 1000
2131
+ ? '<1s'
2132
+ : s.elapsedMs < 60000
2133
+ ? `${Math.floor(s.elapsedMs / 1000)}s`
2134
+ : `${Math.floor(s.elapsedMs / 60000)}m`
2135
+ lines.push(
2136
+ ` ${statusIcon[s.status] || '⚪'} ${s.id} · ${s.provider}/${s.model} · ${elapsed} · ${s.task.slice(0, 50)}`,
2137
+ )
2138
+ }
2139
+
2140
+ if (total > 10) {
2141
+ lines.push(` ... and ${total - 10} more`)
2142
+ }
2083
2143
 
2084
- Mipham Code includes 4 agent-type tools for structured workflows:
2144
+ lines.push('', 'Run `mipham agents` for the full interactive dashboard with j/k navigation.')
2085
2145
 
2086
- ┌──────────┬──────────────────────────────────────┬──────────┬──────────────────────────┐
2087
- │ Agent │ Category │ Permission │ Parameters │
2088
- ├──────────┼──────────┼────────────┼────────────────────────────────┤
2089
- │ Agent │ agent │ ask │ description*, prompt*, │
2090
- │ │ │ │ subagent_type (optional) │
2091
- │ Skill │ agent │ auto │ skill*, args (optional) │
2092
- │ Plan │ agent │ auto │ (no required params) │
2093
- │ Memory │ agent │ auto │ action* (read|write|list), │
2094
- │ │ │ │ name, content │
2095
- └──────────┴──────────┴────────────┴────────────────────────────────┘
2146
+ return { content: lines.join('\n') }
2147
+ }
2096
2148
 
2097
- ── Agent Descriptions ──
2149
+ const bgCmd: CommandHandler = (ctx, args) => {
2150
+ const prompt = args.join(' ')
2151
+ if (!prompt.trim()) {
2152
+ return {
2153
+ content: `Usage: /bg <prompt>
2098
2154
 
2099
- Agent Launch a sub-agent for complex, multi-step tasks.
2100
- Types: general-purpose, Explore, Plan, code-reviewer, etc.
2155
+ Spawn a background agent to work on a task while you continue chatting.
2101
2156
 
2102
- Skill Invoke a named skill (11 built-in, custom via .SKILL.md).
2103
- Skills extend AI capabilities with specialized instructions.
2157
+ Examples:
2158
+ /bg Run the full test suite and report failures
2159
+ /bg Audit the src/ directory for security issues
2160
+ /bg Generate API documentation from JSDoc comments
2104
2161
 
2105
- Plan Enter plan mode read-only analysis and design.
2106
- Use before complex changes. Exit with /no-plan.
2162
+ Background agents appear in the Agent View dashboard (/agents).`,
2163
+ }
2164
+ }
2107
2165
 
2108
- Memory Persistent memory read/write across sessions.
2109
- Stored in ~/.mipham/memory/ as markdown files.
2166
+ const agentViewManager = ctx.engine.getAgentViewManager?.()
2167
+ if (!agentViewManager) {
2168
+ return { content: 'Agent View manager is initializing...' }
2169
+ }
2110
2170
 
2111
- ── Architecture ──
2171
+ const session = agentViewManager.create(prompt, prompt, {
2172
+ provider: ctx.providerId,
2173
+ model: ctx.modelId,
2174
+ })
2112
2175
 
2113
- User QueryEngine Agent Tool → Sub-agent → Tool Results → User
2176
+ agentViewManager.addMessage(session.id, { role: 'user', content: prompt })
2177
+ agentViewManager.updateStatus(session.id, 'working')
2114
2178
 
2115
- Agent tools are dispatched by the AI during conversations.
2116
- The AI decides when to use them — you can also request them directly.
2179
+ return {
2180
+ content: `✓ Background agent spawned: ${session.id}
2117
2181
 
2118
- Full multi-agent orchestration (Workflow tool) is in the M3 milestone.
2182
+ Task: ${session.task.slice(0, 80)}
2183
+ Provider: ${session.provider} / ${session.model}
2184
+ Status: working
2119
2185
 
2120
- Use /tools to see all 16 tools, including the 4 agent tools.`,
2121
- })
2186
+ Use /agents to view all background sessions.
2187
+ Run \`mipham agents\` for the interactive dashboard.`,
2188
+ }
2189
+ }
2122
2190
 
2123
2191
  // ═══════════════════════════════════════════════════════════════
2124
2192
  // Artifacts
@@ -2286,6 +2354,7 @@ registry.set('/login', loginCmd)
2286
2354
  registry.set('/logout', logoutCmd)
2287
2355
  registry.set('/feedback', feedbackCmd)
2288
2356
  registry.set('/agents', agentsCmd)
2357
+ registry.set('/bg', bgCmd)
2289
2358
 
2290
2359
  // Artifacts
2291
2360
  registry.set('/artifact', artifactBaseCmd)