@goodandready/dsh-moa 0.2.14 → 0.2.16
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/README.md +7 -0
- package/lib/best-effort.js +38 -0
- package/lib/client.js +7 -4
- package/lib/file-workspace.js +62 -36
- package/lib/history.js +14 -4
- package/lib/index.js +17 -382
- package/lib/live-canvas.js +22 -0
- package/lib/moa-candidates.js +225 -0
- package/lib/moa-prompts.js +7 -4
- package/lib/moa-runner.js +38 -252
- package/lib/pricing.js +10 -1
- package/lib/routes.js +408 -0
- package/lib/updater.js +280 -0
- package/package.json +1 -2
- package/docs/README.ru.md +0 -287
- package/docs/README.zh.md +0 -243
- package/docs/design/DESIGN.md +0 -73
- package/docs/plans/47-client-js-split-plan.md +0 -57
- package/docs/plans/55-resilience-plan.md +0 -33
- package/docs/plans/59-power-pack-plan.md +0 -73
package/README.md
CHANGED
|
@@ -282,6 +282,13 @@ npm test
|
|
|
282
282
|
|
|
283
283
|
---
|
|
284
284
|
|
|
285
|
+
|
|
286
|
+
## 🛠️ Internal Tooling & Development
|
|
287
|
+
|
|
288
|
+
For local verification and package integrity validation:
|
|
289
|
+
- `npm test`: runs the full test suite (95 tests)
|
|
290
|
+
- `./deploy.sh`: local infrastructure validation script (verifies package size, identity parity in package.json/cordis.patch.yml/client.js, and tests). Excluded from the published npm package.
|
|
291
|
+
|
|
285
292
|
## 📄 License
|
|
286
293
|
|
|
287
294
|
MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run a non-critical side effect; never throw.
|
|
3
|
+
* Logs at debug/warn when a logger is provided or via console.debug.
|
|
4
|
+
*
|
|
5
|
+
* @param {string} label - Context identifier for telemetry / diagnostics
|
|
6
|
+
* @param {Function} fn - Operation to execute
|
|
7
|
+
* @param {object} [logger] - Optional logger instance
|
|
8
|
+
*/
|
|
9
|
+
export function bestEffort(label, fn, logger) {
|
|
10
|
+
const log = (err) => {
|
|
11
|
+
try {
|
|
12
|
+
if (logger && typeof logger.debug === 'function') {
|
|
13
|
+
logger.debug(`[dsh-moa:bestEffort] ${label}:`, err?.message || err)
|
|
14
|
+
} else if (typeof console !== 'undefined' && typeof console.debug === 'function') {
|
|
15
|
+
console.debug(`[dsh-moa:bestEffort] ${label}:`, err?.message || err)
|
|
16
|
+
}
|
|
17
|
+
} catch {
|
|
18
|
+
// Logger itself must never crash the process
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const result = fn()
|
|
24
|
+
if (result && typeof result.then === 'function') {
|
|
25
|
+
return result.then(
|
|
26
|
+
(value) => value,
|
|
27
|
+
(err) => {
|
|
28
|
+
log(err)
|
|
29
|
+
return undefined
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
return result
|
|
34
|
+
} catch (err) {
|
|
35
|
+
log(err)
|
|
36
|
+
return undefined
|
|
37
|
+
}
|
|
38
|
+
}
|
package/lib/client.js
CHANGED
|
@@ -1444,7 +1444,7 @@ window.__ModuleLoader__.load({
|
|
|
1444
1444
|
const snapshot = React.useSyncExternalStore
|
|
1445
1445
|
? React.useSyncExternalStore(
|
|
1446
1446
|
React.useMemo(() => (cb) => (scope ? scope.subscribe(cb) : () => {}), [scope]),
|
|
1447
|
-
React.useCallback(() => (scope ? scope.getSnapshot() : { status: '
|
|
1447
|
+
React.useCallback(() => (scope ? scope.getSnapshot() : { status: 'unavailable' }), [scope]),
|
|
1448
1448
|
React.useCallback(() => ({ status: 'loading' }), [])
|
|
1449
1449
|
)
|
|
1450
1450
|
: null
|
|
@@ -1461,8 +1461,11 @@ window.__ModuleLoader__.load({
|
|
|
1461
1461
|
const [leaderboardPresetFilter, setLeaderboardPresetFilter] = React.useState('all')
|
|
1462
1462
|
|
|
1463
1463
|
React.useEffect(() => {
|
|
1464
|
-
if (!scope || !snapshot)
|
|
1465
|
-
|
|
1464
|
+
if (!scope || !snapshot) {
|
|
1465
|
+
setStatus('unavailable')
|
|
1466
|
+
return
|
|
1467
|
+
}
|
|
1468
|
+
const snapStatus = snapshot.status || 'unavailable'
|
|
1466
1469
|
if (snapStatus === 'ready') {
|
|
1467
1470
|
const val = snapshot.value || {}
|
|
1468
1471
|
if (Array.isArray(val.presets)) setPresets(val.presets)
|
|
@@ -1730,7 +1733,7 @@ window.__ModuleLoader__.load({
|
|
|
1730
1733
|
return () => {
|
|
1731
1734
|
for (const off of undo) {
|
|
1732
1735
|
if (typeof off === 'function') {
|
|
1733
|
-
try { off() } catch {}
|
|
1736
|
+
try { off() } catch (offErr) { /* unsubscribe best-effort */ }
|
|
1734
1737
|
}
|
|
1735
1738
|
}
|
|
1736
1739
|
}
|
package/lib/file-workspace.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { bestEffort } from './best-effort.js'
|
|
1
2
|
import vm from 'node:vm'
|
|
2
3
|
import fs from 'node:fs/promises'
|
|
3
4
|
import path from 'node:path'
|
|
@@ -90,6 +91,18 @@ function sanitizePath(filePath) {
|
|
|
90
91
|
* Collects readable text files from current workspace to feed as context for candidate models (#6).
|
|
91
92
|
* Ignores node_modules, .git, .moa, binary files, large bundles.
|
|
92
93
|
*/
|
|
94
|
+
/**
|
|
95
|
+
* Formats collected project files into structured Markdown code blocks
|
|
96
|
+
* suitable for inclusion in candidate system prompts.
|
|
97
|
+
*/
|
|
98
|
+
export function formatProjectContext(projectFiles = []) {
|
|
99
|
+
if (!Array.isArray(projectFiles) || projectFiles.length === 0) return ''
|
|
100
|
+
const fence = '```'
|
|
101
|
+
return projectFiles
|
|
102
|
+
.map((f) => `### File: ${f.relativePath}\n${fence}\n${f.content}\n${fence}`)
|
|
103
|
+
.join('\n\n')
|
|
104
|
+
}
|
|
105
|
+
|
|
93
106
|
export async function collectProjectContext(baseDir, maxCharBudget = 16000) {
|
|
94
107
|
if (!baseDir) return { files: [], totalChars: 0 }
|
|
95
108
|
|
|
@@ -102,51 +115,63 @@ export async function collectProjectContext(baseDir, maxCharBudget = 16000) {
|
|
|
102
115
|
const collected = []
|
|
103
116
|
let totalChars = 0
|
|
104
117
|
|
|
118
|
+
let skippedFiles = 0
|
|
119
|
+
const skippedList = []
|
|
120
|
+
|
|
105
121
|
async function scan(currentDir, relPrefix = '') {
|
|
106
122
|
if (totalChars >= maxCharBudget) return
|
|
123
|
+
let list
|
|
107
124
|
try {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
125
|
+
list = await fs.readdir(currentDir, { withFileTypes: true })
|
|
126
|
+
} catch (readDirErr) {
|
|
127
|
+
skippedFiles++
|
|
128
|
+
skippedList.push(relPrefix || '.')
|
|
129
|
+
return
|
|
130
|
+
}
|
|
114
131
|
|
|
115
|
-
|
|
116
|
-
|
|
132
|
+
for (const item of list) {
|
|
133
|
+
if (totalChars >= maxCharBudget) break
|
|
134
|
+
// Skip all dotfiles, including .env and .env.* variants: their
|
|
135
|
+
// contents must never be shipped to third-party model providers.
|
|
136
|
+
if (item.name.startsWith('.')) continue
|
|
117
137
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
138
|
+
const rel = relPrefix ? `${relPrefix}/${item.name}` : item.name
|
|
139
|
+
const full = path.join(currentDir, item.name)
|
|
140
|
+
|
|
141
|
+
if (item.isDirectory()) {
|
|
142
|
+
if (!ignoreDirs.has(item.name)) {
|
|
143
|
+
await scan(full, rel)
|
|
144
|
+
}
|
|
145
|
+
} else if (item.isFile()) {
|
|
146
|
+
const ext = path.extname(item.name).toLowerCase()
|
|
147
|
+
if (codeExts.has(ext)) {
|
|
148
|
+
try {
|
|
149
|
+
const stat = await fs.stat(full)
|
|
150
|
+
if (stat.size > 100000) continue // skip files > 100kb
|
|
151
|
+
const content = await fs.readFile(full, 'utf8')
|
|
152
|
+
const budgetRemaining = maxCharBudget - totalChars
|
|
153
|
+
const truncated = content.length > budgetRemaining ? content.slice(0, budgetRemaining) + '\n... [truncated]' : content
|
|
154
|
+
totalChars += truncated.length
|
|
155
|
+
collected.push({ relativePath: rel, content: truncated, size: stat.size })
|
|
156
|
+
} catch (fileErr) {
|
|
157
|
+
skippedFiles++
|
|
158
|
+
skippedList.push(rel)
|
|
134
159
|
}
|
|
135
160
|
}
|
|
136
161
|
}
|
|
137
|
-
}
|
|
162
|
+
}
|
|
138
163
|
}
|
|
139
164
|
|
|
140
165
|
await scan(baseDir)
|
|
141
|
-
return { files: collected, totalChars }
|
|
166
|
+
return { files: collected, totalChars, skippedFiles, skippedList }
|
|
142
167
|
}
|
|
143
168
|
|
|
144
169
|
/**
|
|
145
170
|
* Checks if a task prompt is an iterative refinement / modification
|
|
146
171
|
* of an existing project rather than a greenfield project creation (#4).
|
|
147
172
|
*/
|
|
148
|
-
export function isRefinementTask(userPrompt = '', projectFiles =
|
|
149
|
-
if (
|
|
173
|
+
export function isRefinementTask(userPrompt = '', projectFiles = null) {
|
|
174
|
+
if (Array.isArray(projectFiles) && projectFiles.length === 0) return false
|
|
150
175
|
const p = userPrompt.toLowerCase().trim()
|
|
151
176
|
if (!p) return false
|
|
152
177
|
|
|
@@ -212,7 +237,7 @@ export async function promoteCandidateWorkspace(baseDir, winningIndex, options =
|
|
|
212
237
|
const entries = await getFilesRecursively(winningDir)
|
|
213
238
|
if (entries.length === 0) {
|
|
214
239
|
if (!options.keepMoa) {
|
|
215
|
-
|
|
240
|
+
await bestEffort('clean moaRoot on empty candidate', () => fs.rm(moaRoot, { recursive: true, force: true }))
|
|
216
241
|
}
|
|
217
242
|
return []
|
|
218
243
|
}
|
|
@@ -235,7 +260,7 @@ export async function promoteCandidateWorkspace(baseDir, winningIndex, options =
|
|
|
235
260
|
return promoted
|
|
236
261
|
} catch (err) {
|
|
237
262
|
if (!options.keepMoa) {
|
|
238
|
-
|
|
263
|
+
await bestEffort('clean moaRoot on promote error', () => fs.rm(moaRoot, { recursive: true, force: true }))
|
|
239
264
|
}
|
|
240
265
|
return []
|
|
241
266
|
}
|
|
@@ -247,9 +272,7 @@ export async function promoteCandidateWorkspace(baseDir, winningIndex, options =
|
|
|
247
272
|
export async function cleanMoaWorkspaces(baseDir) {
|
|
248
273
|
if (!baseDir) return
|
|
249
274
|
const moaRoot = path.join(baseDir, '.moa')
|
|
250
|
-
|
|
251
|
-
await fs.rm(moaRoot, { recursive: true, force: true })
|
|
252
|
-
} catch {}
|
|
275
|
+
await bestEffort('cleanMoaWorkspaces', () => fs.rm(moaRoot, { recursive: true, force: true }))
|
|
253
276
|
}
|
|
254
277
|
|
|
255
278
|
async function getFilesRecursively(dir) {
|
|
@@ -257,13 +280,14 @@ async function getFilesRecursively(dir) {
|
|
|
257
280
|
try {
|
|
258
281
|
const stat = await fs.stat(dir)
|
|
259
282
|
if (!stat.isDirectory()) return []
|
|
260
|
-
} catch {
|
|
283
|
+
} catch (statErr) {
|
|
261
284
|
return []
|
|
262
285
|
}
|
|
263
286
|
|
|
264
287
|
async function scan(current, relPrefix = '') {
|
|
288
|
+
let list
|
|
265
289
|
try {
|
|
266
|
-
|
|
290
|
+
list = await fs.readdir(current, { withFileTypes: true })
|
|
267
291
|
for (const item of list) {
|
|
268
292
|
const rel = relPrefix ? `${relPrefix}/${item.name}` : item.name
|
|
269
293
|
const full = path.join(current, item.name)
|
|
@@ -273,7 +297,9 @@ async function getFilesRecursively(dir) {
|
|
|
273
297
|
results.push(rel)
|
|
274
298
|
}
|
|
275
299
|
}
|
|
276
|
-
} catch {
|
|
300
|
+
} catch (readErr) {
|
|
301
|
+
return
|
|
302
|
+
}
|
|
277
303
|
}
|
|
278
304
|
await scan(dir)
|
|
279
305
|
return results
|
package/lib/history.js
CHANGED
|
@@ -15,7 +15,7 @@ const DEFAULT_HISTORY_FILE = path.join(DEFAULT_HISTORY_DIR, 'moa-history.jsonl')
|
|
|
15
15
|
/** Max history file size before rotation (10 MB) */
|
|
16
16
|
export const MAX_HISTORY_BYTES = 10 * 1024 * 1024
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
function getHistoryFilePath(customDir) {
|
|
19
19
|
if (customDir) {
|
|
20
20
|
return path.join(customDir, '.moa-history.jsonl')
|
|
21
21
|
}
|
|
@@ -33,7 +33,7 @@ export function rotateHistoryFileIfNeeded(filePath = DEFAULT_HISTORY_FILE, maxBy
|
|
|
33
33
|
if (stat.size >= maxBytes) {
|
|
34
34
|
const backupPath = `${filePath}.1`
|
|
35
35
|
if (fs.existsSync(backupPath)) {
|
|
36
|
-
try { fs.unlinkSync(backupPath) } catch {}
|
|
36
|
+
try { fs.unlinkSync(backupPath) } catch (unlinkErr) { /* backup unlink best-effort */ }
|
|
37
37
|
}
|
|
38
38
|
fs.renameSync(filePath, backupPath)
|
|
39
39
|
return true
|
|
@@ -241,7 +241,7 @@ export function getMoaRunById(runId, filePath = DEFAULT_HISTORY_FILE) {
|
|
|
241
241
|
try {
|
|
242
242
|
const item = JSON.parse(lines[i])
|
|
243
243
|
if (item && item.id === runId) return item
|
|
244
|
-
} catch {}
|
|
244
|
+
} catch (parseErr) { /* corrupt line skipped */ }
|
|
245
245
|
}
|
|
246
246
|
return null
|
|
247
247
|
} catch {
|
|
@@ -270,7 +270,7 @@ export function exportMoaHistory(filePath = DEFAULT_HISTORY_FILE, options = {})
|
|
|
270
270
|
if (!item) continue
|
|
271
271
|
if (presetFilter && presetFilter !== 'all' && item.preset !== presetFilter) continue
|
|
272
272
|
records.push(item)
|
|
273
|
-
} catch {}
|
|
273
|
+
} catch (parseErr) { /* corrupt line skipped */ }
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
if (format === 'csv') {
|
|
@@ -298,3 +298,13 @@ export function exportMoaHistory(filePath = DEFAULT_HISTORY_FILE, options = {})
|
|
|
298
298
|
return options?.format === 'csv' ? '' : '[]'
|
|
299
299
|
}
|
|
300
300
|
}
|
|
301
|
+
|
|
302
|
+
export function candidatesForHistory(referenceOutputs) {
|
|
303
|
+
return (referenceOutputs || []).map((r) => ({
|
|
304
|
+
provider: r.slot?.provider || "",
|
|
305
|
+
model: r.slot?.model || "",
|
|
306
|
+
files: (r.files || []).map((f) => f.relativePath),
|
|
307
|
+
usage: r.usage || { inputTokens: 0, outputTokens: 0 },
|
|
308
|
+
costUsd: r.costUsd || 0,
|
|
309
|
+
}))
|
|
310
|
+
}
|