@astrale-os/cli 0.7.0-alpha.0 → 0.8.0-alpha.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 (105) hide show
  1. package/README.md +3 -2
  2. package/dist/astrale.js +118 -95
  3. package/package.json +2 -3
  4. package/src/commands/studio.ts +13 -0
  5. package/src/lib/__tests__/view-assets.test.ts +114 -0
  6. package/src/lib/instance.ts +0 -11
  7. package/src/lib/login-flow.ts +4 -41
  8. package/src/lib/view/assets.ts +64 -0
  9. package/src/lib/view/server.ts +4 -37
  10. package/studio/client/dist/assets/index-BaqEuIQJ.js +109 -0
  11. package/studio/client/dist/assets/index-jRdExahh.css +1 -0
  12. package/studio/client/dist/index.html +2 -2
  13. package/studio/server/agent/ask.test.ts +74 -0
  14. package/studio/server/agent/ask.ts +20 -22
  15. package/studio/server/agent/bridge/client.test.ts +49 -0
  16. package/studio/server/agent/bridge/client.ts +37 -0
  17. package/studio/server/agent/bridge/grant.test.ts +62 -0
  18. package/studio/server/agent/bridge/grant.ts +84 -0
  19. package/studio/server/agent/bridge/routes.test.ts +151 -0
  20. package/studio/server/agent/bridge/routes.ts +148 -0
  21. package/studio/server/agent/{bridge-mcp.ts → bridge/stdio.ts} +27 -21
  22. package/studio/server/agent/conversation.test.ts +84 -0
  23. package/studio/server/agent/conversation.ts +110 -0
  24. package/studio/server/agent/{types.ts → harness/adapter.ts} +63 -12
  25. package/studio/server/agent/harness/claude/adapter.test.ts +338 -0
  26. package/studio/server/agent/harness/claude/adapter.ts +71 -0
  27. package/studio/server/agent/harness/claude/ask.ts +114 -0
  28. package/studio/server/agent/harness/claude/capabilities.ts +27 -0
  29. package/studio/server/agent/harness/claude/command.ts +63 -0
  30. package/studio/server/agent/harness/claude/events.ts +215 -0
  31. package/studio/server/agent/harness/claude/loadout.ts +132 -0
  32. package/studio/server/agent/harness/claude/mcp.test.ts +36 -0
  33. package/studio/server/agent/harness/claude/mcp.ts +41 -0
  34. package/studio/server/agent/harness/claude/skills.ts +43 -0
  35. package/studio/server/agent/harness/codex/adapter.test.ts +269 -0
  36. package/studio/server/agent/harness/codex/adapter.ts +116 -0
  37. package/studio/server/agent/harness/codex/ask.test.ts +176 -0
  38. package/studio/server/agent/harness/codex/ask.ts +181 -0
  39. package/studio/server/agent/harness/codex/command.test.ts +69 -0
  40. package/studio/server/agent/harness/codex/command.ts +43 -0
  41. package/studio/server/agent/harness/codex/events.test.ts +94 -0
  42. package/studio/server/agent/harness/codex/events.ts +132 -0
  43. package/studio/server/agent/harness/codex/exec.ts +113 -0
  44. package/studio/server/agent/harness/codex/loadout.ts +63 -0
  45. package/studio/server/agent/harness/codex/mcp.test.ts +32 -0
  46. package/studio/server/agent/harness/codex/mcp.ts +27 -0
  47. package/studio/server/agent/harness/codex/models.test.ts +167 -0
  48. package/studio/server/agent/harness/codex/models.ts +211 -0
  49. package/studio/server/agent/harness/codex/skills.ts +33 -0
  50. package/studio/server/agent/harness/gateway/config.test.ts +98 -0
  51. package/studio/server/{state/harness-gateway.ts → agent/harness/gateway/config.ts} +30 -8
  52. package/studio/server/agent/harness/gateway/token.test.ts +133 -0
  53. package/studio/server/agent/harness/gateway/token.ts +166 -0
  54. package/studio/server/agent/harness/mock/adapter.ts +190 -0
  55. package/studio/server/agent/harness/mock/domain-edit.ts +41 -0
  56. package/studio/server/agent/harness/process.test.ts +46 -0
  57. package/studio/server/agent/harness/process.ts +104 -0
  58. package/studio/server/agent/harness/registry.ts +37 -0
  59. package/studio/server/agent/harness/selection.test.ts +75 -0
  60. package/studio/server/agent/harness/selection.ts +77 -0
  61. package/studio/server/agent/harness/skills.test.ts +95 -0
  62. package/studio/server/agent/harness/skills.ts +100 -0
  63. package/studio/server/agent/layout.test.ts +95 -0
  64. package/studio/server/agent/notify.ts +12 -0
  65. package/studio/server/agent/{schema-map.ts → prompts/anchors.ts} +2 -2
  66. package/studio/server/agent/prompts/ask.ts +34 -0
  67. package/studio/server/agent/{prompt.ts → prompts/system.ts} +3 -107
  68. package/studio/server/agent/prompts/turn.ts +57 -0
  69. package/studio/server/agent/routes.test.ts +176 -0
  70. package/studio/server/agent/routes.ts +214 -0
  71. package/studio/server/agent/run/completion.ts +193 -0
  72. package/studio/server/agent/run/coordinator.test.ts +337 -0
  73. package/studio/server/agent/run/coordinator.ts +104 -0
  74. package/studio/server/agent/run/live-state.ts +73 -0
  75. package/studio/server/agent/run/preparation.ts +162 -0
  76. package/studio/server/agent/run/transcript.test.ts +47 -0
  77. package/studio/server/agent/run/transcript.ts +30 -0
  78. package/studio/server/agent/run/usage.test.ts +46 -0
  79. package/studio/server/{state → agent/run}/usage.ts +4 -4
  80. package/studio/server/agent/stream.ts +37 -0
  81. package/studio/server/agent/{session-id.ts → telemetry.ts} +1 -1
  82. package/studio/server/api-agent-loadout.test.ts +69 -0
  83. package/studio/server/api.ts +3 -155
  84. package/studio/server/index.ts +6 -5
  85. package/studio/server/state/comments.test.ts +29 -0
  86. package/studio/server/state/comments.ts +3 -3
  87. package/studio/server/state/settings.test.ts +78 -0
  88. package/studio/server/state/settings.ts +28 -1
  89. package/studio/shared/agent-effort.test.ts +12 -0
  90. package/studio/shared/agent-effort.ts +15 -0
  91. package/studio/shared/agent-models.test.ts +9 -0
  92. package/studio/shared/agent-models.ts +14 -0
  93. package/studio/shared/settings-values.test.ts +18 -0
  94. package/studio/shared/settings-values.ts +20 -0
  95. package/studio/shared/types.ts +58 -7
  96. package/src/connect-core.test.ts +0 -42
  97. package/src/connect-core.ts +0 -53
  98. package/studio/client/dist/assets/index-DAC1a9vW.js +0 -109
  99. package/studio/client/dist/assets/index-huaFafBC.css +0 -1
  100. package/studio/server/agent/bridge.ts +0 -188
  101. package/studio/server/agent/claude.ts +0 -678
  102. package/studio/server/agent/mock.ts +0 -186
  103. package/studio/server/agent/registry.ts +0 -29
  104. package/studio/server/agent/runner.ts +0 -487
  105. package/studio/server/state/harness-token.ts +0 -0
@@ -1,186 +0,0 @@
1
- /**
2
- * agent/mock.ts — a free, deterministic stand-in for a real harness. It exists
3
- * so the whole live loop (submit → stream events → edit → re-render → reply →
4
- * merge) can be exercised end-to-end without spending real agent credits.
5
- *
6
- * It behaves like a minimal real agent: reads the open threads, makes ONE real
7
- * edit to the domain schema (so the studio's watch→re-render fires), narrates a
8
- * few activity events, then returns a final message carrying the same
9
- * machine-state ```json``` reply block a real agent would emit (so the runner's
10
- * existing mergeReply path is exercised identically). It writes domain source
11
- * directly — it is standing in for the external agent actor, not the studio.
12
- */
13
- import { readFileSync, writeFileSync, readdirSync, existsSync } from 'node:fs'
14
- import { join } from 'node:path'
15
-
16
- import type { Comment } from '../../shared/types'
17
- import type { AgentHarness, AgentTurnInput, AgentTurnResult, AskInput, AskResult } from './types'
18
-
19
- import { readComments } from '../state/comments'
20
-
21
- function sleep(ms: number, signal: AbortSignal): Promise<void> {
22
- return new Promise((resolve) => {
23
- const t = setTimeout(resolve, ms)
24
- signal.addEventListener(
25
- 'abort',
26
- () => {
27
- clearTimeout(t)
28
- resolve()
29
- },
30
- { once: true },
31
- )
32
- })
33
- }
34
-
35
- /** A camelCase identifier derived from free text, for a synthesized prop name. */
36
- function identFromText(text: string, fallback: string): string {
37
- const words = text
38
- .toLowerCase()
39
- .replace(/[^a-z0-9 ]+/g, ' ')
40
- .trim()
41
- .split(/\s+/)
42
- .filter(Boolean)
43
- .slice(0, 3)
44
- if (words.length === 0) return fallback
45
- return words.map((w, i) => (i === 0 ? w : w[0].toUpperCase() + w.slice(1))).join('')
46
- }
47
-
48
- /** Insert a new optional prop into the first `props: {` block of the first
49
- * non-index schema file. Returns the edited file (relative) + prop name. */
50
- function applyMockEdit(root: string, propName: string): { file: string; prop: string } | null {
51
- const schemaDir = join(root, 'schema')
52
- if (!existsSync(schemaDir)) return null
53
- const files = readdirSync(schemaDir).filter((f) => f.endsWith('.ts') && f !== 'index.ts')
54
- for (const f of files) {
55
- const abs = join(schemaDir, f)
56
- const src = readFileSync(abs, 'utf8')
57
- const idx = src.indexOf('props: {')
58
- if (idx < 0) continue
59
- let prop = propName
60
- let n = 2
61
- while (new RegExp(`\\b${prop}\\b\\s*:`).test(src)) prop = `${propName}${n++}`
62
- const insertAt = idx + 'props: {'.length
63
- const line = `\n /** Added by the agent in response to a studio comment. */\n ${prop}: z.string().optional(),`
64
- const next = src.slice(0, insertAt) + line + src.slice(insertAt)
65
- writeFileSync(abs, next)
66
- return { file: `schema/${f}`, prop }
67
- }
68
- return null
69
- }
70
-
71
- export class MockHarness implements AgentHarness {
72
- id = 'mock'
73
- label = 'Mock agent (free)'
74
-
75
- async isAvailable(): Promise<boolean> {
76
- return true
77
- }
78
-
79
- async run(input: AgentTurnInput): Promise<AgentTurnResult> {
80
- const { root, signal, onEvent } = input
81
- // test knobs (env): MODE=error|noblock|openreply|badblock|resumefail, DELAY_MS=extra latency for cancel/concurrency tests
82
- const mode = process.env.DOMAIN_STUDIO_MOCK_MODE || 'normal'
83
- const extraDelay = Number(process.env.DOMAIN_STUDIO_MOCK_DELAY_MS || 0)
84
- // 'resumefail' rejects a RESUME (only when a sessionId is passed) so the runner's
85
- // auto-restart-fresh path can be exercised; the fresh retry (no sessionId) proceeds.
86
- if (mode === 'resumefail' && input.sessionId) {
87
- onEvent({ kind: 'status', text: 'resuming…' })
88
- return {
89
- sessionId: input.sessionId,
90
- finalText: '',
91
- isError: true,
92
- errorMessage: 'mock: no conversation found with session id',
93
- resumeRejected: true,
94
- }
95
- }
96
- const store = readComments(root)
97
- const open = store.comments.filter(
98
- (c) => c.status === 'open' && c.thread.at(-1)?.role !== 'author',
99
- )
100
-
101
- onEvent({ kind: 'status', text: 'session started' })
102
- await sleep(250, signal)
103
- if (extraDelay > 0) await sleep(extraDelay, signal)
104
- if (mode === 'error') throw new Error('mock harness failure (test)')
105
- onEvent({
106
- kind: 'thinking',
107
- text: `Reviewing ${open.length} open thread(s) and the current schema.`,
108
- })
109
- await sleep(300, signal)
110
- onEvent({ kind: 'tool', text: 'Read', tool: 'Read', target: '.domain-studio/comments.json' })
111
- await sleep(250, signal)
112
-
113
- // one real edit so the studio re-renders
114
- const seed = open[0]?.thread.at(-1)?.text ?? 'note'
115
- const editRes = signal.aborted ? null : applyMockEdit(root, identFromText(seed, 'agentNote'))
116
- if (editRes) {
117
- onEvent({ kind: 'tool', text: 'Edit', tool: 'Edit', target: editRes.file })
118
- await sleep(300, signal)
119
- }
120
- onEvent({
121
- kind: 'message',
122
- text: editRes
123
- ? `Added a \`${editRes.prop}\` property to \`${editRes.file}\` and answered the open threads.`
124
- : 'Answered the open threads.',
125
- })
126
-
127
- // final machine-state block (identical shape to a real agent reply)
128
- const replied: Comment[] = open.map((c) => ({
129
- ...c,
130
- status: 'closed',
131
- thread: [
132
- ...c.thread,
133
- {
134
- id: crypto.randomUUID(),
135
- role: 'author' as const,
136
- type: 'text' as const,
137
- text: editRes
138
- ? `Done — implemented this by adding \`${editRes.prop}\` to \`${editRes.file}\`. (mock agent)`
139
- : 'Acknowledged. (mock agent)',
140
- },
141
- ],
142
- }))
143
- const machine = {
144
- schemaVersion: store.schemaVersion,
145
- // 'openreply' leaves threads OPEN with an author entry (clarifying-question loop test)
146
- comments: replied.map((c) => ({
147
- id: c.id,
148
- anchors: c.anchors,
149
- status: mode === 'openreply' ? 'open' : c.status,
150
- thread: c.thread,
151
- })),
152
- }
153
- const finalText =
154
- mode === 'noblock'
155
- ? 'I reviewed the open threads and made the edit. (no machine-state block — resilience test)'
156
- : mode === 'badblock'
157
- ? 'I made the edit.\n\n```json\n{ this is : not valid json, ]\n```\n' // malformed-block resilience test
158
- : `I reviewed the open threads and made the edit.\n\n\`\`\`json\n${JSON.stringify(machine, null, 2)}\n\`\`\`\n`
159
-
160
- return {
161
- sessionId: input.sessionId ?? 'mock-session',
162
- finalText,
163
- costUsd: 0,
164
- numTurns: 1,
165
- isError: false,
166
- }
167
- }
168
-
169
- /** Fake streamed answer for a side-question (free plumbing test of the Ask loop). */
170
- async ask(input: AskInput): Promise<AskResult> {
171
- const forked = input.sessionId ? `(forked from ${input.sessionId.slice(0, 8)}…) ` : '(fresh) '
172
- const parts = [
173
- forked,
174
- 'This is a mock answer to your side question. ',
175
- 'In a real run, Haiku would answer here from the inherited conversation context.',
176
- ]
177
- let text = ''
178
- for (const p of parts) {
179
- if (input.signal.aborted) break
180
- text += p
181
- input.onDelta(p)
182
- await sleep(180, input.signal)
183
- }
184
- return { text, isError: false }
185
- }
186
- }
@@ -1,29 +0,0 @@
1
- import type { AgentHarness } from './types'
2
-
3
- /**
4
- * agent/registry.ts — picks the active harness. Default is Claude Code; set
5
- * DOMAIN_STUDIO_HARNESS=mock for the free deterministic stand-in (tests/dev),
6
- * or =claude explicitly. New harnesses (codex, …) register here.
7
- */
8
- import { ClaudeCodeHarness } from './claude'
9
- import { MockHarness } from './mock'
10
-
11
- const harnesses: Record<string, () => AgentHarness> = {
12
- claude: () => new ClaudeCodeHarness(),
13
- mock: () => new MockHarness(),
14
- }
15
-
16
- let active: AgentHarness | null = null
17
-
18
- export function getHarness(): AgentHarness {
19
- if (active) return active
20
- const want = (process.env.DOMAIN_STUDIO_HARNESS || 'claude').toLowerCase()
21
- const make = harnesses[want] ?? harnesses.claude
22
- active = make()
23
- return active
24
- }
25
-
26
- /** All registered harnesses (id + label) — for the (currently locked) UI selector. */
27
- export function listHarnesses(): { id: string; label: string }[] {
28
- return Object.entries(harnesses).map(([id, make]) => ({ id, label: make().label }))
29
- }