@xelauvas/xela-cli 0.3.1 → 0.4.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 (137) hide show
  1. package/bin/xela.js +0 -12
  2. package/local/README.md +112 -0
  3. package/local/start-qwen.sh +47 -0
  4. package/local/start-tunnel.sh +36 -0
  5. package/local/xela-local.sh +139 -0
  6. package/package.json +9 -5
  7. package/run.sh +33 -0
  8. package/src/Tool.ts +1 -1
  9. package/src/_shims/_generated_stubs/_universal_stub.mjs +168 -0
  10. package/src/_shims/_generated_stubs/stub_346f96741420f62caece15b196e233c6.mjs +19 -0
  11. package/src/_shims/_generated_stubs/stub_4b79af2a6ac1af5a53a55fd666b2bbb4.mjs +3 -0
  12. package/src/_shims/_generated_stubs/stub_94345ddeadacc184ff895597ec5abf71.mjs +6 -0
  13. package/src/_shims/_generated_stubs/stub_99fa0bc272643c549d8a2ec6d578436f.mjs +3 -0
  14. package/src/_shims/_generated_stubs/stub_e3451b131fcaf272a2a75270b9a427a3.mjs +9 -0
  15. package/src/_shims/_generated_stubs/text_stub.mjs +1 -0
  16. package/src/_shims/bun_bundle.js +79 -4
  17. package/src/_shims/cjs_stub.cjs +32 -10
  18. package/src/_shims/loader.js +104 -5
  19. package/src/_shims/openai_adapter.ts +446 -230
  20. package/src/_shims/register.js +16 -3
  21. package/src/assistant/gate.ts +15 -0
  22. package/src/assistant/index.ts +89 -0
  23. package/src/bootstrap/state.ts +6 -1
  24. package/src/bridge/peerSessions.ts +18 -0
  25. package/src/cli/print.ts +5 -0
  26. package/src/commands/assistant/assistant.ts +17 -0
  27. package/src/commands/assistant/index.ts +11 -0
  28. package/src/commands/brief.ts +3 -1
  29. package/src/commands/buddy/buddy.ts +88 -0
  30. package/src/commands/buddy/index.ts +12 -0
  31. package/src/commands/model/model.tsx +8 -23
  32. package/src/commands/peers/index.ts +11 -0
  33. package/src/commands/peers/peers.ts +24 -0
  34. package/src/commands/proactive/proactive.ts +64 -0
  35. package/src/commands/proactive.ts +12 -0
  36. package/src/commands/workflows/index.ts +12 -0
  37. package/src/commands/workflows/workflows.ts +36 -0
  38. package/src/commands.ts +4 -8
  39. package/src/components/Messages.tsx +1 -1
  40. package/src/components/ModelPicker.tsx +4 -34
  41. package/src/components/Spinner.tsx +54 -1
  42. package/src/components/StructuredDiff/colorDiff.ts +3 -1
  43. package/src/components/messages/SnipBoundaryMessage.tsx +15 -0
  44. package/src/components/messages/UserCrossSessionMessage.tsx +26 -0
  45. package/src/components/permissions/PermissionRequest.tsx +4 -2
  46. package/src/components/permissions/ReviewArtifactPermissionRequest/ReviewArtifactPermissionRequest.tsx +96 -0
  47. package/src/constants/prompts.ts +26 -0
  48. package/src/ink/terminal.ts +9 -1
  49. package/src/jobs/classifier.ts +103 -0
  50. package/src/memdir/paths.ts +13 -1
  51. package/src/proactive/index.ts +158 -0
  52. package/src/proactive/useProactive.ts +78 -0
  53. package/src/screens/REPL.tsx +28 -8
  54. package/src/services/api/claude.ts +6 -1
  55. package/src/services/compact/autoCompact.ts +18 -4
  56. package/src/services/compact/cachedMCConfig.ts +1 -0
  57. package/src/services/compact/cachedMicrocompact.ts +113 -0
  58. package/src/services/compact/compact.ts +22 -2
  59. package/src/services/compact/reactiveCompact.ts +251 -0
  60. package/src/services/compact/snipCompact.ts +282 -0
  61. package/src/services/compact/snipProjection.ts +50 -0
  62. package/src/services/contextCollapse/index.ts +322 -0
  63. package/src/services/contextCollapse/operations.ts +187 -0
  64. package/src/services/contextCollapse/persist.ts +40 -0
  65. package/src/services/extractMemories/extractMemories.ts +11 -2
  66. package/src/services/skillSearch/featureCheck.ts +12 -0
  67. package/src/services/skillSearch/localSearch.ts +98 -0
  68. package/src/services/skillSearch/prefetch.ts +125 -0
  69. package/src/services/skillSearch/remoteSkillLoader.ts +9 -0
  70. package/src/services/skillSearch/remoteSkillState.ts +21 -0
  71. package/src/services/skillSearch/signals.ts +7 -0
  72. package/src/services/skillSearch/telemetry.ts +9 -0
  73. package/src/setup.ts +10 -5
  74. package/src/skills/bundled/dream.ts +31 -0
  75. package/src/skills/bundled/hunter.ts +45 -0
  76. package/src/skills/bundled/index.ts +10 -23
  77. package/src/skills/bundled/runSkillGenerator.ts +61 -0
  78. package/src/skills/mcpSkills.ts +101 -0
  79. package/src/tasks/LocalWorkflowTask/LocalWorkflowTask.ts +301 -0
  80. package/src/tasks/MonitorMcpTask/MonitorMcpTask.ts +273 -0
  81. package/src/tasks.ts +22 -8
  82. package/src/tools/AgentTool/runAgent.ts +11 -0
  83. package/src/tools/BriefTool/BriefTool.ts +2 -0
  84. package/src/tools/CtxInspectTool/CtxInspectTool.ts +102 -0
  85. package/src/tools/DiscoverSkillsTool/DiscoverSkillsTool.ts +78 -0
  86. package/src/tools/DiscoverSkillsTool/prompt.ts +3 -0
  87. package/src/tools/ListPeersTool/ListPeersTool.ts +89 -0
  88. package/src/tools/MCPTool/MCPTool.ts +6 -0
  89. package/src/tools/MonitorTool/MonitorTool.ts +273 -0
  90. package/src/tools/MonitorTool/UI.tsx +25 -0
  91. package/src/tools/MonitorTool/events.ts +84 -0
  92. package/src/tools/MonitorTool/prompt.ts +45 -0
  93. package/src/tools/OverflowTestTool/OverflowTestTool.ts +73 -0
  94. package/src/tools/PushNotificationTool/PushNotificationTool.ts +119 -0
  95. package/src/tools/ReviewArtifactTool/ReviewArtifactTool.ts +93 -0
  96. package/src/tools/ReviewArtifactTool/prompt.ts +5 -0
  97. package/src/tools/SendUserFileTool/SendUserFileTool.ts +87 -0
  98. package/src/tools/SendUserFileTool/prompt.ts +7 -0
  99. package/src/tools/SleepTool/SleepTool.ts +126 -0
  100. package/src/tools/SnipTool/SnipTool.ts +90 -0
  101. package/src/tools/SnipTool/prompt.ts +5 -0
  102. package/src/tools/SubscribePRTool/SubscribePRTool.ts +115 -0
  103. package/src/tools/TerminalCaptureTool/TerminalCaptureTool.ts +129 -0
  104. package/src/tools/TerminalCaptureTool/prompt.ts +8 -0
  105. package/src/tools/ToolSearchTool/prompt.ts +1 -1
  106. package/src/tools/VerifyPlanExecutionTool/VerifyPlanExecutionTool.ts +99 -0
  107. package/src/tools/WebBrowserTool/WebBrowserPanel.tsx +10 -0
  108. package/src/tools/WebBrowserTool/WebBrowserTool.ts +190 -0
  109. package/src/tools/WebBrowserTool/prompt.ts +16 -0
  110. package/src/tools/WorkflowTool/UI.tsx +27 -0
  111. package/src/tools/WorkflowTool/WorkflowTool.ts +401 -0
  112. package/src/tools/WorkflowTool/bundled/index.ts +88 -0
  113. package/src/tools/WorkflowTool/constants.ts +7 -0
  114. package/src/tools/WorkflowTool/createWorkflowCommand.ts +84 -0
  115. package/src/tools/WorkflowTool/prompt.ts +38 -0
  116. package/src/tools/WorkflowTool/runtime.ts +331 -0
  117. package/src/tools.ts +36 -31
  118. package/src/utils/betas.ts +5 -0
  119. package/src/utils/context.ts +9 -0
  120. package/src/utils/conversationRecovery.ts +1 -1
  121. package/src/utils/forkedAgent.ts +4 -0
  122. package/src/utils/logoV2Utils.ts +7 -3
  123. package/src/utils/messageQueueManager.ts +23 -9
  124. package/src/utils/model/model.ts +10 -9
  125. package/src/utils/model/modelOptions.ts +0 -21
  126. package/src/utils/model/validateModel.ts +0 -19
  127. package/src/utils/permissions/permissionSetup.ts +5 -0
  128. package/src/utils/permissions/permissions.ts +25 -0
  129. package/src/utils/permissions/yolo-classifier-prompts/auto_mode_system_prompt.txt +38 -0
  130. package/src/utils/permissions/yolo-classifier-prompts/permissions_external.txt +25 -0
  131. package/src/utils/permissions/yoloClassifier.ts +28 -6
  132. package/src/utils/plugins/officialMarketplaceStartupCheck.ts +9 -0
  133. package/src/utils/systemThemeWatcher.ts +62 -0
  134. package/src/utils/udsClient.ts +115 -0
  135. package/src/utils/udsMessaging.ts +157 -0
  136. package/src/utils/yaml.ts +251 -5
  137. package/src/utils/model/providerModels.ts +0 -168
@@ -6,7 +6,9 @@ register('./loader.js', import.meta.url);
6
6
  // Provide a global `require` for CJS-style requires in ESM context
7
7
  // (Bun supports this but Node ESM doesn't)
8
8
  import { dirname, resolve, extname } from 'node:path';
9
- import { existsSync, readFileSync, statSync } from 'node:fs';
9
+ import { existsSync, readFileSync, statSync, mkdirSync, writeFileSync } from 'node:fs';
10
+ import { tmpdir } from 'node:os';
11
+ import { createHash } from 'node:crypto';
10
12
  import { transformSync } from 'esbuild';
11
13
  import { fileURLToPath } from 'node:url';
12
14
  import Module from 'node:module';
@@ -92,9 +94,20 @@ Module._resolveFilename = function(request, parent, isMain, options) {
92
94
  }
93
95
  }
94
96
  // Last resort: return CJS stub for missing modules
95
- // (DCE'd feature-gated code)
97
+ // (DCE'd feature-gated code). One tiny file per specifier so the stub can
98
+ // report which module was missing (XELA_LOADER_DEBUG=1).
96
99
  const CJS_STUB_PATH = resolve(dirname(fileURLToPath(import.meta.url)), 'cjs_stub.cjs');
97
- return CJS_STUB_PATH;
100
+ try {
101
+ const dir = resolve(tmpdir(), 'xela-cjs-stubs');
102
+ mkdirSync(dir, { recursive: true });
103
+ const file = resolve(dir, createHash('sha1').update(request).digest('hex').slice(0, 16) + '.cjs');
104
+ if (!existsSync(file)) {
105
+ writeFileSync(file, `module.exports = require(${JSON.stringify(CJS_STUB_PATH)}).__makeStub(${JSON.stringify(request)});\n`);
106
+ }
107
+ return file;
108
+ } catch {
109
+ return CJS_STUB_PATH;
110
+ }
98
111
  }
99
112
  };
100
113
 
@@ -0,0 +1,15 @@
1
+ import { isEnvTruthy } from '../utils/envUtils.js'
2
+
3
+ /**
4
+ * Entitlement gate for assistant (KAIROS) mode. Upstream this asks a remote
5
+ * feature service; a local build has nobody to ask, so it is on unless
6
+ * explicitly disabled.
7
+ */
8
+ export async function isKairosEnabled(): Promise<boolean> {
9
+ if (isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_ASSISTANT)) return false
10
+ return true
11
+ }
12
+
13
+ export function isKairosEnabledSync(): boolean {
14
+ return !isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_ASSISTANT)
15
+ }
@@ -0,0 +1,89 @@
1
+ import { existsSync, readFileSync } from 'fs'
2
+ import { join } from 'path'
3
+ import { getCwd } from '../utils/cwd.js'
4
+ import { isEnvTruthy } from '../utils/envUtils.js'
5
+
6
+ /**
7
+ * Assistant mode: a long-lived session that behaves like a personal
8
+ * assistant (brief status messages, proactive ticks, an in-process team it
9
+ * can delegate to). Activated by `--assistant`, CLAUDE_CODE_ASSISTANT=1, or a
10
+ * project that ships `.claude/agents/assistant.md`.
11
+ */
12
+ type AssistantState = { forced: boolean; teamInitialized: boolean }
13
+ const state: AssistantState = ((globalThis as unknown as { __xelaAssistant?: AssistantState })
14
+ .__xelaAssistant ??= { forced: false, teamInitialized: false })
15
+
16
+ export const ASSISTANT_TEAM_NAME = 'assistant'
17
+
18
+ export function markAssistantForced(): void {
19
+ state.forced = true
20
+ }
21
+
22
+ export function isAssistantForced(): boolean {
23
+ return state.forced
24
+ }
25
+
26
+ function assistantDefinitionPath(): string {
27
+ return join(getCwd(), '.claude', 'agents', 'assistant.md')
28
+ }
29
+
30
+ export function isAssistantMode(): boolean {
31
+ if (state.forced) return true
32
+ if (isEnvTruthy(process.env.CLAUDE_CODE_ASSISTANT)) return true
33
+ try {
34
+ return existsSync(assistantDefinitionPath())
35
+ } catch {
36
+ return false
37
+ }
38
+ }
39
+
40
+ export type AssistantTeamContext = {
41
+ teamName: string
42
+ definitionPath: string | null
43
+ }
44
+
45
+ /**
46
+ * Pre-seeds the in-process team so `Agent(name: "...")` can spawn teammates
47
+ * without TeamCreate. The team itself is created lazily by the Agent tool;
48
+ * here we only record that assistant mode owns it.
49
+ */
50
+ export async function initializeAssistantTeam(): Promise<AssistantTeamContext> {
51
+ state.teamInitialized = true
52
+ const path = assistantDefinitionPath()
53
+ return { teamName: ASSISTANT_TEAM_NAME, definitionPath: existsSync(path) ? path : null }
54
+ }
55
+
56
+ /** How assistant mode got turned on, for diagnostics. */
57
+ export function getAssistantActivationPath(): string {
58
+ if (state.forced) return 'flag'
59
+ if (isEnvTruthy(process.env.CLAUDE_CODE_ASSISTANT)) return 'env'
60
+ return existsSync(assistantDefinitionPath()) ? 'agent_file' : 'none'
61
+ }
62
+
63
+ export function isAssistantTeamInitialized(): boolean {
64
+ return state.teamInitialized
65
+ }
66
+
67
+ function readAssistantDefinition(): string | null {
68
+ try {
69
+ const path = assistantDefinitionPath()
70
+ if (!existsSync(path)) return null
71
+ const raw = readFileSync(path, 'utf8')
72
+ // Drop YAML frontmatter; the body is the instructions.
73
+ const m = /^---\n[\s\S]*?\n---\n?/.exec(raw)
74
+ return (m ? raw.slice(m[0].length) : raw).trim() || null
75
+ } catch {
76
+ return null
77
+ }
78
+ }
79
+
80
+ export function getAssistantSystemPromptAddendum(): string {
81
+ const custom = readAssistantDefinition()
82
+ return [
83
+ '# Assistant Mode',
84
+ 'You are running as a long-lived personal assistant session. Keep the user informed with short status messages rather than long reports, remember what they ask you to remember, and delegate independent work to teammates with the Agent tool when it helps. Between prompts you may receive <tick> check-ins: do something useful or call Sleep.',
85
+ custom ? `## Assistant instructions\n\n${custom}` : null,
86
+ ]
87
+ .filter(Boolean)
88
+ .join('\n\n')
89
+ }
@@ -1756,4 +1756,9 @@ export function setPromptId(id: string | null): void {
1756
1756
  STATE.promptId = id
1757
1757
  }
1758
1758
 
1759
- export const isReplBridgeActive = undefined;
1759
+ export function isReplBridgeActive(): boolean {
1760
+ return (STATE as { replBridgeActive?: boolean }).replBridgeActive === true
1761
+ }
1762
+ export function setReplBridgeActive(active: boolean): void {
1763
+ ;(STATE as { replBridgeActive?: boolean }).replBridgeActive = active
1764
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Remote Control ("bridge") peers relay through Anthropic's servers and are
3
+ * not reachable from a local build. Kept so SendMessage's `bridge:` branch
4
+ * fails cleanly.
5
+ */
6
+ export async function postInterClaudeMessage(
7
+ _sessionId: string,
8
+ _message: string,
9
+ ): Promise<{ ok: boolean; error?: string }> {
10
+ return {
11
+ ok: false,
12
+ error: 'Remote Control is not available in this build; use uds:<socket> for sessions on this machine (see ListPeers)',
13
+ }
14
+ }
15
+
16
+ export async function listBridgePeers(): Promise<Array<{ sessionId: string; title?: string }>> {
17
+ return []
18
+ }
package/src/cli/print.ts CHANGED
@@ -2422,6 +2422,11 @@ function runHeadlessStreaming(
2422
2422
  }
2423
2423
  }
2424
2424
  } catch (error) {
2425
+ // XELA_LOADER_DEBUG=1: the result message only carries the error text;
2426
+ // the stack is what locates a stripped-module failure
2427
+ if (process.env.XELA_LOADER_DEBUG && error instanceof Error) {
2428
+ process.stderr.write(`[print] execution error: ${error.stack}\n`)
2429
+ }
2425
2430
  // Emit error result message before shutting down
2426
2431
  // Write directly to structuredIO to ensure immediate delivery
2427
2432
  try {
@@ -0,0 +1,17 @@
1
+ import { getAssistantSystemPromptAddendum, isAssistantForced, isAssistantMode, isAssistantTeamInitialized } from '../../assistant/index.js'
2
+ import type { LocalCommandCall } from '../../types/command.js'
3
+
4
+ export const call: LocalCommandCall = async args => {
5
+ if ((args || '').trim() === 'prompt') {
6
+ return { type: 'text', value: getAssistantSystemPromptAddendum() }
7
+ }
8
+ const on = isAssistantMode()
9
+ const lines = [
10
+ on
11
+ ? `Assistant mode is on${isAssistantForced() ? ' (forced by --assistant)' : ''}${isAssistantTeamInitialized() ? ', team ready' : ''}.`
12
+ : 'Assistant mode is off.',
13
+ 'Enable it with `xela-local --assistant`, `CLAUDE_CODE_ASSISTANT=1`, or a `.claude/agents/assistant.md` file in the project (its body becomes the assistant instructions).',
14
+ '`/assistant prompt` shows the system-prompt addendum used in this mode.',
15
+ ]
16
+ return { type: 'text', value: lines.join('\n') }
17
+ }
@@ -0,0 +1,11 @@
1
+ import type { Command } from '../../commands.js'
2
+
3
+ const assistant = {
4
+ type: 'local',
5
+ name: 'assistant',
6
+ description: 'Show assistant-mode status and how to enable it',
7
+ supportsNonInteractive: true,
8
+ load: () => import('./assistant.js'),
9
+ } satisfies Command
10
+
11
+ export default assistant
@@ -27,7 +27,9 @@ const briefConfigSchema = lazySchema(() =>
27
27
  type BriefConfig = z.infer<ReturnType<typeof briefConfigSchema>>
28
28
 
29
29
  const DEFAULT_BRIEF_CONFIG: BriefConfig = {
30
- enable_slash_command: false,
30
+ // Local builds have no remote config to turn this on; the command is the
31
+ // only way for a user to opt into brief mode outside assistant mode.
32
+ enable_slash_command: true,
31
33
  }
32
34
 
33
35
  // No TTL — this gate controls slash-command *visibility*, not a kill switch.
@@ -0,0 +1,88 @@
1
+ import { companionUserId, getCompanion, roll } from '../../buddy/companion.js'
2
+ import { RARITY_STARS, type StoredCompanion } from '../../buddy/types.js'
3
+ import type { LocalCommandCall } from '../../types/command.js'
4
+ import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js'
5
+
6
+ const NAMES = [
7
+ 'Pixel', 'Mochi', 'Biscuit', 'Noodle', 'Pebble', 'Waffle', 'Comet', 'Tofu', 'Ziggy', 'Maple',
8
+ 'Bagel', 'Clover', 'Dot', 'Fig', 'Juniper', 'Kiwi', 'Lumen', 'Nimbus', 'Olive', 'Quill',
9
+ ]
10
+
11
+ const PERSONALITIES = [
12
+ 'quietly curious, hums while you compile, delighted by a green test run',
13
+ 'dramatic about typos, loyal to tabs, secretly proud of every commit',
14
+ 'calm and deliberate, counts semicolons for fun, allergic to force-push',
15
+ 'chaotic optimist, cheers for every build, naps during long installs',
16
+ 'dry wit, keeps a mental list of TODOs you forgot, forgives merge conflicts',
17
+ 'tiny perfectionist, loves a tidy diff, sighs at console.log left behind',
18
+ 'warm and encouraging, believes every bug is just a feature that got lost',
19
+ 'nocturnal, sharpest after midnight, suspicious of regexes that work first try',
20
+ ]
21
+
22
+ function hash(s: string): number {
23
+ let h = 2166136261
24
+ for (let i = 0; i < s.length; i++) {
25
+ h ^= s.charCodeAt(i)
26
+ h = Math.imul(h, 16777619)
27
+ }
28
+ return h >>> 0
29
+ }
30
+
31
+ function hatch(): StoredCompanion {
32
+ const seed = hash(companionUserId() + ':soul')
33
+ return {
34
+ name: NAMES[seed % NAMES.length]!,
35
+ personality: PERSONALITIES[(seed >>> 8) % PERSONALITIES.length]!,
36
+ hatchedAt: Date.now(),
37
+ }
38
+ }
39
+
40
+ function describe(): string {
41
+ const c = getCompanion()
42
+ if (!c) return 'No companion yet. Run `/buddy` to hatch one.'
43
+ const muted = getGlobalConfig().companionMuted ? ' (muted)' : ''
44
+ const stats = Object.entries(c.stats)
45
+ .map(([k, v]) => `${k} ${v}`)
46
+ .join(' · ')
47
+ return [
48
+ `${c.name}${muted} — ${c.shiny ? 'shiny ' : ''}${c.rarity} ${c.species} ${RARITY_STARS[c.rarity]}`,
49
+ `Eyes: ${c.eye} · hat: ${c.hat}`,
50
+ `Personality: ${c.personality}`,
51
+ `Stats: ${stats}`,
52
+ `Hatched ${new Date(c.hatchedAt).toLocaleDateString()}. Commands: /buddy rename <name>, /buddy mute, /buddy unmute, /buddy release`,
53
+ ].join('\n')
54
+ }
55
+
56
+ export const call: LocalCommandCall = async args => {
57
+ const [action, ...rest] = (args || '').trim().split(/\s+/).filter(Boolean)
58
+ const config = getGlobalConfig()
59
+ switch ((action || '').toLowerCase()) {
60
+ case 'rename': {
61
+ const name = rest.join(' ').trim()
62
+ if (!config.companion) return { type: 'text', value: 'No companion to rename. Run `/buddy` first.' }
63
+ if (!name) return { type: 'text', value: 'Usage: /buddy rename <name>' }
64
+ saveGlobalConfig(prev => ({ ...prev, companion: { ...prev.companion!, name: name.slice(0, 24) } }))
65
+ return { type: 'text', value: `Renamed to ${name.slice(0, 24)}.\n\n${describe()}` }
66
+ }
67
+ case 'mute':
68
+ saveGlobalConfig(prev => ({ ...prev, companionMuted: true }))
69
+ return { type: 'text', value: 'Companion muted. `/buddy unmute` brings it back.' }
70
+ case 'unmute':
71
+ saveGlobalConfig(prev => ({ ...prev, companionMuted: false }))
72
+ return { type: 'text', value: describe() }
73
+ case 'release':
74
+ if (!config.companion) return { type: 'text', value: 'No companion to release.' }
75
+ saveGlobalConfig(prev => ({ ...prev, companion: undefined, companionMuted: undefined }))
76
+ return { type: 'text', value: 'Released. Run `/buddy` to hatch a new one (same bones, new soul).' }
77
+ default: {
78
+ if (config.companion) return { type: 'text', value: describe() }
79
+ const soul = hatch()
80
+ saveGlobalConfig(prev => ({ ...prev, companion: soul, companionMuted: false }))
81
+ const { bones } = roll(companionUserId())
82
+ return {
83
+ type: 'text',
84
+ value: `An egg cracks… it's a ${bones.shiny ? 'shiny ' : ''}${bones.rarity} ${bones.species} ${RARITY_STARS[bones.rarity]}!\n\n${describe()}`,
85
+ }
86
+ }
87
+ }
88
+ }
@@ -0,0 +1,12 @@
1
+ import type { Command } from '../../commands.js'
2
+
3
+ const buddy = {
4
+ type: 'local',
5
+ name: 'buddy',
6
+ description: 'Your terminal companion: hatch it, see it, rename, mute or release it',
7
+ argumentHint: '[rename <name> | mute | unmute | release]',
8
+ supportsNonInteractive: true,
9
+ load: () => import('./buddy.js'),
10
+ } satisfies Command
11
+
12
+ export default buddy
@@ -14,8 +14,7 @@ import { MODEL_ALIASES } from '../../utils/model/aliases.js';
14
14
  import { checkOpus1mAccess, checkSonnet1mAccess } from '../../utils/model/check1mAccess.js';
15
15
  import { getDefaultMainLoopModelSetting, isOpus1mMergeEnabled, renderDefaultModelSetting } from '../../utils/model/model.js';
16
16
  import { isModelAllowed } from '../../utils/model/modelAllowlist.js';
17
- import { validateModel } from '../../utils/model/validateModel.js'
18
- import { isOpenAIBackend, isClaudeModelName } from '../../utils/model/providerModels.js';
17
+ import { validateModel } from '../../utils/model/validateModel.js';
19
18
  function ModelPickerWrapper(t0) {
20
19
  const $ = _c(17);
21
20
  const {
@@ -142,30 +141,13 @@ function SetModelAndClose({
142
141
  const model = args === 'default' ? null : args;
143
142
  React.useEffect(() => {
144
143
  async function handleModelChange(): Promise<void> {
145
- if (model && !isOpenAIBackend() && !isModelAllowed(model)) {
144
+ if (model && !isModelAllowed(model)) {
146
145
  onDone(`Model '${model}' is not available. Your organization restricts model selection.`, {
147
146
  display: 'system'
148
147
  });
149
148
  return;
150
149
  }
151
150
 
152
- // Skip validation for default model
153
- if (!model) {
154
- setModel(null);
155
- return;
156
- }
157
-
158
- // OpenAI backend: skip all Claude-specific checks, go straight to validation
159
- if (isOpenAIBackend()) {
160
- const { valid, error: validationError } = await validateModel(model);
161
- if (valid) {
162
- setModel(model);
163
- } else {
164
- onDone(validationError || `Model '${model}' not found`, { display: 'system' });
165
- }
166
- return;
167
- }
168
-
169
151
  // @[MODEL LAUNCH]: Update check for 1M access.
170
152
  if (model && isOpus1mUnavailable(model)) {
171
153
  onDone(`Opus 4.6 with 1M context is not available for your account. Learn more: https://code.claude.com/docs/en/model-config#extended-context-with-1m`, {
@@ -180,6 +162,12 @@ function SetModelAndClose({
180
162
  return;
181
163
  }
182
164
 
165
+ // Skip validation for default model
166
+ if (!model) {
167
+ setModel(null);
168
+ return;
169
+ }
170
+
183
171
  // Skip validation for known aliases - they're predefined and should work
184
172
  if (isKnownAlias(model)) {
185
173
  setModel(model);
@@ -303,9 +291,6 @@ export const call: LocalJSXCommandCall = async (onDone, _context, args) => {
303
291
  return <ModelPickerWrapper onDone={onDone} />;
304
292
  };
305
293
  function renderModelLabel(model: string | null): string {
306
- if (isOpenAIBackend() && model && !isClaudeModelName(model)) {
307
- return model;
308
- }
309
294
  const rendered = renderDefaultModelSetting(model ?? getDefaultMainLoopModelSetting());
310
295
  return model === null ? `${rendered} (default)` : rendered;
311
296
  }
@@ -0,0 +1,11 @@
1
+ import type { Command } from '../../commands.js'
2
+
3
+ const peers = {
4
+ type: 'local',
5
+ name: 'peers',
6
+ description: 'List other Xela sessions on this machine that can receive messages',
7
+ supportsNonInteractive: true,
8
+ load: () => import('./peers.js'),
9
+ } satisfies Command
10
+
11
+ export default peers
@@ -0,0 +1,24 @@
1
+ import type { LocalCommandCall } from '../../types/command.js'
2
+ import { listAllLiveSessions } from '../../utils/udsClient.js'
3
+ import { getUdsMessagingSocketPath } from '../../utils/udsMessaging.js'
4
+
5
+ export const call: LocalCommandCall = async () => {
6
+ const sessions = await listAllLiveSessions()
7
+ const lines: string[] = []
8
+ const self = getUdsMessagingSocketPath()
9
+ lines.push(self ? `This session: uds:${self}` : 'This session has no messaging socket (UDS inbox off).')
10
+ const peers = sessions.filter(s => !s.isSelf)
11
+ if (peers.length === 0) {
12
+ lines.push('No other sessions found.')
13
+ } else {
14
+ lines.push('Other sessions:')
15
+ for (const s of peers) {
16
+ const age = s.startedAt ? `${Math.round((Date.now() - s.startedAt) / 60000)} min` : '?'
17
+ lines.push(
18
+ ` pid ${s.pid} ${s.reachable ? 'reachable' : 'no socket'} ${s.cwd ?? ''}${s.name ? ` [${s.name}]` : ''} up ${age}` +
19
+ (s.messagingSocketPath ? `\n → SendMessage to: uds:${s.messagingSocketPath}` : ''),
20
+ )
21
+ }
22
+ }
23
+ return { type: 'text', value: lines.join('\n') }
24
+ }
@@ -0,0 +1,64 @@
1
+ import {
2
+ activateProactive,
3
+ deactivateProactive,
4
+ getProactiveState,
5
+ getTickPrompt,
6
+ isProactiveActive,
7
+ isProactivePaused,
8
+ pauseProactive,
9
+ PROACTIVE_TICK_INTERVAL_MS,
10
+ recordTick,
11
+ resumeProactive,
12
+ scheduleNextTick,
13
+ } from '../../proactive/index.js'
14
+ import type { LocalCommandCall } from '../../types/command.js'
15
+ import { enqueue } from '../../utils/messageQueueManager.js'
16
+
17
+ function status(): string {
18
+ const s = getProactiveState()
19
+ if (!s.active) return 'Proactive mode is off. `/proactive on` starts it.'
20
+ return [
21
+ `Proactive mode is ${isProactivePaused() ? 'paused' : 'on'} (since ${s.activatedAt ? new Date(s.activatedAt).toLocaleTimeString() : '?'}, ${s.tickCount} ticks, every ${Math.round(PROACTIVE_TICK_INTERVAL_MS / 60_000)} min).`,
22
+ s.contextBlocked ? 'Ticks are blocked because the context is full; run /compact or /clear.' : null,
23
+ ]
24
+ .filter(Boolean)
25
+ .join('\n')
26
+ }
27
+
28
+ function kickTick(): void {
29
+ recordTick()
30
+ enqueue({ value: getTickPrompt(), mode: 'prompt', priority: 'later' })
31
+ }
32
+
33
+ export const call: LocalCommandCall = async args => {
34
+ const action = (args || 'status').trim().toLowerCase()
35
+ switch (action) {
36
+ case 'on':
37
+ case 'start':
38
+ if (!isProactiveActive()) {
39
+ // activateProactive schedules the first tick; useProactive (REPL) or
40
+ // print.ts (headless) delivers it.
41
+ activateProactive('command')
42
+ return { type: 'text', value: `Proactive mode on. The model gets a tick prompt shortly, then every ${Math.round(PROACTIVE_TICK_INTERVAL_MS / 60_000)} min or whenever its Sleep ends. \`/proactive off\` stops it.` }
43
+ }
44
+ if (isProactivePaused()) resumeProactive()
45
+ return { type: 'text', value: status() }
46
+ case 'off':
47
+ case 'stop':
48
+ deactivateProactive()
49
+ return { type: 'text', value: 'Proactive mode off.' }
50
+ case 'pause':
51
+ pauseProactive()
52
+ return { type: 'text', value: status() }
53
+ case 'resume':
54
+ resumeProactive()
55
+ if (isProactiveActive()) scheduleNextTick(0)
56
+ return { type: 'text', value: status() }
57
+ case 'tick':
58
+ if (!isProactiveActive()) activateProactive('command')
59
+ kickTick()
60
+ return { type: 'text', value: 'Tick sent.' }
61
+ default:
62
+ return { type: 'text', value: status() }
63
+ }
64
+ }
@@ -0,0 +1,12 @@
1
+ import type { Command } from '../commands.js'
2
+
3
+ const proactive = {
4
+ type: 'local',
5
+ name: 'proactive',
6
+ description: 'Toggle proactive mode: the model keeps working between prompts (on | off | pause | resume | status)',
7
+ argumentHint: '[on|off|pause|resume|status]',
8
+ supportsNonInteractive: true,
9
+ load: () => import('./proactive/proactive.js'),
10
+ } satisfies Command
11
+
12
+ export default proactive
@@ -0,0 +1,12 @@
1
+ import type { Command } from '../../commands.js'
2
+
3
+ const workflows = {
4
+ type: 'local',
5
+ name: 'workflows',
6
+ aliases: ['workflow', 'wf'],
7
+ description: 'List workflow runs in this session and bundled/saved workflows',
8
+ supportsNonInteractive: true,
9
+ load: () => import('./workflows.js'),
10
+ } satisfies Command
11
+
12
+ export default workflows
@@ -0,0 +1,36 @@
1
+ import { getWorkflowTasks } from '../../tasks/LocalWorkflowTask/LocalWorkflowTask.js'
2
+ import {
3
+ initBundledWorkflows,
4
+ listBundledWorkflows,
5
+ } from '../../tools/WorkflowTool/bundled/index.js'
6
+ import type { LocalCommandCall } from '../../types/command.js'
7
+
8
+ export const call: LocalCommandCall = async (_args, context) => {
9
+ initBundledWorkflows()
10
+ const runs = getWorkflowTasks(context.getAppState())
11
+ const lines: string[] = []
12
+ if (runs.length === 0) {
13
+ lines.push('No workflow runs in this session.')
14
+ } else {
15
+ lines.push('Workflow runs:')
16
+ for (const run of runs) {
17
+ const elapsed = Math.round(((run.endTime ?? Date.now()) - run.startTime) / 1000)
18
+ const done = run.agents.filter(a => a.status === 'completed').length
19
+ lines.push(
20
+ ` ${run.id} ${run.name} ${run.status} ${elapsed}s agents ${done}/${run.agents.length}` +
21
+ (run.currentPhase ? ` phase: ${run.currentPhase}` : '') +
22
+ (run.error ? ` error: ${run.error}` : ''),
23
+ )
24
+ for (const a of run.agents) {
25
+ lines.push(` - ${a.label} [${a.status}]${a.phase ? ` (${a.phase})` : ''}${a.error ? `: ${a.error}` : ''}`)
26
+ }
27
+ lines.push(` output: ${run.outputFile}`)
28
+ }
29
+ }
30
+ const bundled = listBundledWorkflows()
31
+ lines.push('')
32
+ lines.push('Available workflows (run with the slash command below, or the Workflow tool):')
33
+ for (const wf of bundled) lines.push(` /${wf.name} ${wf.description}`)
34
+ lines.push(' Saved workflows: any .js file in .claude/workflows/ (project) or workflows/ (config dir) becomes a slash command')
35
+ return { type: 'text', value: lines.join('\n') }
36
+ }
package/src/commands.ts CHANGED
@@ -83,11 +83,7 @@ const voiceCommand = feature('VOICE_MODE')
83
83
  const forceSnip = feature('HISTORY_SNIP')
84
84
  ? require('./commands/force-snip.js').default
85
85
  : null
86
- const workflowsCmd = feature('WORKFLOW_SCRIPTS')
87
- ? (
88
- require('./commands/workflows/index.js') as typeof import('./commands/workflows/index.js')
89
- ).default
90
- : null
86
+ const workflowsCmd = feature('WORKFLOW_SCRIPTS') ? workflowsCommandImpl : null
91
87
  const webCmd = feature('CCR_REMOTE_SETUP')
92
88
  ? (
93
89
  require('./commands/remote-setup/index.js') as typeof import('./commands/remote-setup/index.js')
@@ -159,6 +155,8 @@ import {
159
155
  getDynamicSkills,
160
156
  } from './skills/loadSkillsDir.js'
161
157
  import { getBundledSkills } from './skills/bundledSkills.js'
158
+ import workflowsCommandImpl from './commands/workflows/index.js'
159
+ import { getWorkflowCommands as getWorkflowCommandsImpl } from './tools/WorkflowTool/createWorkflowCommand.js'
162
160
  import { getBuiltinPluginSkillCommands } from './plugins/builtinPlugins.js'
163
161
  import {
164
162
  getPluginCommands,
@@ -399,9 +397,7 @@ async function getSkills(cwd: string): Promise<{
399
397
 
400
398
  /* eslint-disable @typescript-eslint/no-require-imports */
401
399
  const getWorkflowCommands = feature('WORKFLOW_SCRIPTS')
402
- ? (
403
- require('./tools/WorkflowTool/createWorkflowCommand.js') as typeof import('./tools/WorkflowTool/createWorkflowCommand.js')
404
- ).getWorkflowCommands
400
+ ? getWorkflowCommandsImpl
405
401
  : null
406
402
  /* eslint-enable @typescript-eslint/no-require-imports */
407
403
 
@@ -79,7 +79,7 @@ const LogoHeader = React.memo(function LogoHeader(t0) {
79
79
  /* eslint-disable @typescript-eslint/no-require-imports */
80
80
  const proactiveModule = feature('PROACTIVE') || feature('KAIROS') ? require('../proactive/index.js') : null;
81
81
  const BRIEF_TOOL_NAME: string | null = feature('KAIROS') || feature('KAIROS_BRIEF') ? (require('../tools/BriefTool/prompt.js') as typeof import('../tools/BriefTool/prompt.js')).BRIEF_TOOL_NAME : null;
82
- const SEND_USER_FILE_TOOL_NAME: string | null = feature('KAIROS') ? (require('../tools/SendUserFileTool/prompt.js') as typeof import('../tools/SendUserFileTool/prompt.js')).SEND_USER_FILE_TOOL_NAME : null;
82
+ const SEND_USER_FILE_TOOL_NAME: string | null = feature('KAIROS') || feature('KAIROS_PUSH_NOTIFICATION') ? (require('../tools/SendUserFileTool/prompt.js') as typeof import('../tools/SendUserFileTool/prompt.js')).SEND_USER_FILE_TOOL_NAME : null;
83
83
 
84
84
  /* eslint-enable @typescript-eslint/no-require-imports */
85
85
  import { VirtualMessageList } from './VirtualMessageList.js';