mixdog 0.9.9 → 0.9.11
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/package.json +1 -1
- package/scripts/agent-tag-reuse-smoke.mjs +12 -8
- package/scripts/bench/cache-probe-tasks.json +8 -0
- package/scripts/bench/lead-review-tasks-r3.json +1 -1
- package/scripts/bench/r4-mixed-tasks.json +1 -1
- package/scripts/bench/review-tasks.json +1 -1
- package/scripts/build-runtime-windows.ps1 +242 -242
- package/scripts/recall-usecase-cases.json +1 -1
- package/scripts/smoke-runtime-negative.ps1 +106 -106
- package/scripts/tool-smoke.mjs +109 -0
- package/src/defaults/mixdog-config.template.json +1 -0
- package/src/mixdog-session-runtime.mjs +8 -0
- package/src/rules/agent/30-explorer.md +18 -20
- package/src/rules/lead/02-channels.md +3 -3
- package/src/runtime/agent/orchestrator/config.mjs +30 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +160 -0
- package/src/runtime/agent/orchestrator/mcp/client.mjs +25 -1
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +24 -9
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +18 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +23 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +79 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +9 -2
- package/src/runtime/agent/orchestrator/session/manager.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +1 -22
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +26 -3
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +29 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +17 -16
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +4 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +30 -4
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +333 -14
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +129 -2
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +58 -18
- package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +56 -83
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +26 -56
- package/src/runtime/channels/lib/webhook/deliveries.mjs +4 -1
- package/src/session-runtime/settings-api.mjs +13 -0
- package/src/session-runtime/tool-catalog.mjs +34 -7
- package/src/standalone/agent-tool/render.mjs +2 -0
- package/src/standalone/agent-tool.mjs +28 -6
- package/src/standalone/explore-tool.mjs +2 -2
- package/src/standalone/memory-runtime-proxy.mjs +85 -21
- package/src/tui/App.jsx +20 -1
- package/src/tui/app/extension-pickers.mjs +6 -3
- package/src/tui/dist/index.mjs +28 -4
- package/src/tui/engine.mjs +2 -0
|
@@ -1,106 +1,106 @@
|
|
|
1
|
-
# smoke-runtime-negative.ps1 — Negative-path tests against released win32-x64
|
|
2
|
-
# runtime tarball. Validates: download, sha256, corrupt-tarball, fresh-extract
|
|
3
|
-
# boot, double-init refusal, hostile-env survival.
|
|
4
|
-
#
|
|
5
|
-
# Env: TAG / OS / ARCH / RUNTIME_RELEASE_REPOSITORY (default tribgames/mixdog)
|
|
6
|
-
|
|
7
|
-
$ErrorActionPreference = 'Stop'
|
|
8
|
-
|
|
9
|
-
$Tag = if ($env:TAG) { $env:TAG } else { 'runtime-v0.4.0' }
|
|
10
|
-
$Os = if ($env:OS) { $env:OS } else { 'win32' }
|
|
11
|
-
$Arch = if ($env:ARCH) { $env:ARCH } else { 'x64' }
|
|
12
|
-
$ReleaseRepo = if ($env:RUNTIME_RELEASE_REPOSITORY) { $env:RUNTIME_RELEASE_REPOSITORY } else { 'tribgames/mixdog' }
|
|
13
|
-
$PgVer = '16.4'
|
|
14
|
-
$PgvectorVer = '0.8.2'
|
|
15
|
-
|
|
16
|
-
$Asset = "mixdog-runtime-${Os}-${Arch}-pg${PgVer}-pgvector${PgvectorVer}.tar.gz"
|
|
17
|
-
$Url = "https://github.com/${ReleaseRepo}/releases/download/${Tag}/${Asset}"
|
|
18
|
-
|
|
19
|
-
$Work = New-Item -ItemType Directory -Force -Path "$env:TEMP\smoke-$([guid]::NewGuid().ToString('N'))" | Select-Object -ExpandProperty FullName
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
Write-Host "==> Negative-path smoke for ${Os}-${Arch} from $Url"
|
|
23
|
-
|
|
24
|
-
Write-Host "==> Test 1: HEAD reaches release asset"
|
|
25
|
-
$resp = Invoke-WebRequest -Uri $Url -Method Head -UseBasicParsing -ErrorAction Stop
|
|
26
|
-
Write-Host " HTTP $($resp.StatusCode)"
|
|
27
|
-
|
|
28
|
-
Write-Host "==> Test 2: full download + sha256"
|
|
29
|
-
$TarPath = Join-Path $Work $Asset
|
|
30
|
-
Invoke-WebRequest -Uri $Url -OutFile $TarPath -UseBasicParsing
|
|
31
|
-
$sha = (Get-FileHash -Algorithm SHA256 $TarPath).Hash.ToLower()
|
|
32
|
-
Write-Host " sha256=$sha"
|
|
33
|
-
|
|
34
|
-
Write-Host "==> Test 3: corrupt tarball — flip a byte and ensure tar errors"
|
|
35
|
-
$CorruptPath = "$Work\corrupt.tar.gz"
|
|
36
|
-
Copy-Item $TarPath $CorruptPath
|
|
37
|
-
$bytes = [byte[]]::new(1); $rand = [System.Random]::new(); $rand.NextBytes($bytes)
|
|
38
|
-
$stream = [System.IO.File]::OpenWrite($CorruptPath)
|
|
39
|
-
$stream.Position = 102400
|
|
40
|
-
$stream.Write($bytes, 0, 1)
|
|
41
|
-
$stream.Close()
|
|
42
|
-
$CorruptDir = "$Work\corrupt"
|
|
43
|
-
New-Item -ItemType Directory -Force -Path $CorruptDir | Out-Null
|
|
44
|
-
$TarProc = Start-Process -FilePath tar -ArgumentList @('-xzf', $CorruptPath, '-C', $CorruptDir.Replace('\','/')) -Wait:$false -PassThru -WindowStyle Hidden
|
|
45
|
-
if (-not $TarProc.WaitForExit(15000)) {
|
|
46
|
-
Stop-Process -Id $TarProc.Id -Force -ErrorAction SilentlyContinue
|
|
47
|
-
Write-Host " PASS: corrupted tarball extraction timed out and was stopped"
|
|
48
|
-
} elseif ($TarProc.ExitCode -eq 0) {
|
|
49
|
-
Write-Host " WARN: corrupted tarball extracted without error (tar gzip recovery)"
|
|
50
|
-
} else {
|
|
51
|
-
Write-Host " PASS: corrupted tarball rejected by tar (exit $($TarProc.ExitCode))"
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
Write-Host "==> Test 4: fresh-extract boot (extension load + distance query)"
|
|
55
|
-
$FreshDir = "$Work\fresh"
|
|
56
|
-
New-Item -ItemType Directory -Force -Path $FreshDir | Out-Null
|
|
57
|
-
& tar -xzf $TarPath -C ($FreshDir.Replace('\','/'))
|
|
58
|
-
if ($LASTEXITCODE -ne 0) { throw "fresh extract failed" }
|
|
59
|
-
|
|
60
|
-
$PgBin = "$FreshDir\bin"
|
|
61
|
-
$Data = "$FreshDir\pgdata"
|
|
62
|
-
$Log = "$FreshDir\pg.log"
|
|
63
|
-
$Port = 55897
|
|
64
|
-
|
|
65
|
-
# Hostile env: minimal PATH (System32 only), no PGROOT/PGDATA
|
|
66
|
-
$SavedPath = $env:PATH
|
|
67
|
-
$SavedPgRoot = $env:PGROOT
|
|
68
|
-
$env:PATH = "$env:SystemRoot\System32;$env:SystemRoot"
|
|
69
|
-
$env:PGROOT = $null
|
|
70
|
-
$env:PGDATA = $null
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
& "$PgBin\postgres.exe" --version
|
|
74
|
-
if ($LASTEXITCODE -ne 0) { throw "FAIL: postgres --version" }
|
|
75
|
-
& "$PgBin\initdb.exe" -D $Data --username=postgres --auth-local=trust --no-locale -E UTF8 | Out-Null
|
|
76
|
-
if ($LASTEXITCODE -ne 0) { throw "FAIL: initdb" }
|
|
77
|
-
& "$PgBin\pg_ctl.exe" -D $Data -o "-p $Port -h 127.0.0.1" -l $Log -w start
|
|
78
|
-
if ($LASTEXITCODE -ne 0) { Get-Content $Log | Select-Object -Last 30; throw "FAIL: pg_ctl start" }
|
|
79
|
-
|
|
80
|
-
try {
|
|
81
|
-
& "$PgBin\psql.exe" -h 127.0.0.1 -p $Port -U postgres -d postgres -c "CREATE EXTENSION vector;" | Out-Null
|
|
82
|
-
if ($LASTEXITCODE -ne 0) { throw "FAIL: CREATE EXTENSION" }
|
|
83
|
-
$ExtV = & "$PgBin\psql.exe" -h 127.0.0.1 -p $Port -U postgres -d postgres -tAc "SELECT extversion FROM pg_extension WHERE extname='vector';"
|
|
84
|
-
if ($ExtV.Trim() -ne $PgvectorVer) { throw "FAIL: extversion='$ExtV' expected='$PgvectorVer'" }
|
|
85
|
-
Write-Host " PASS: fresh-extract boot + vector extension"
|
|
86
|
-
} finally {
|
|
87
|
-
& "$PgBin\pg_ctl.exe" -D $Data -m fast stop 2>$null | Out-Null
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
Write-Host "==> Test 5: second initdb on initialized dir refused"
|
|
91
|
-
$rc = (Start-Process -FilePath "$PgBin\initdb.exe" -ArgumentList "-D",$Data,"-U","postgres" -Wait -PassThru -NoNewWindow).ExitCode
|
|
92
|
-
if ($rc -eq 0) {
|
|
93
|
-
Write-Host " WARN: second initdb succeeded (unexpected)"
|
|
94
|
-
} else {
|
|
95
|
-
Write-Host " PASS: second initdb refused (exit $rc)"
|
|
96
|
-
}
|
|
97
|
-
} finally {
|
|
98
|
-
$env:PATH = $SavedPath
|
|
99
|
-
$env:PGROOT = $SavedPgRoot
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
Write-Host "==> All negative-path smokes: PASS"
|
|
103
|
-
}
|
|
104
|
-
finally {
|
|
105
|
-
Remove-Item -Recurse -Force $Work -ErrorAction SilentlyContinue
|
|
106
|
-
}
|
|
1
|
+
# smoke-runtime-negative.ps1 — Negative-path tests against released win32-x64
|
|
2
|
+
# runtime tarball. Validates: download, sha256, corrupt-tarball, fresh-extract
|
|
3
|
+
# boot, double-init refusal, hostile-env survival.
|
|
4
|
+
#
|
|
5
|
+
# Env: TAG / OS / ARCH / RUNTIME_RELEASE_REPOSITORY (default tribgames/mixdog)
|
|
6
|
+
|
|
7
|
+
$ErrorActionPreference = 'Stop'
|
|
8
|
+
|
|
9
|
+
$Tag = if ($env:TAG) { $env:TAG } else { 'runtime-v0.4.0' }
|
|
10
|
+
$Os = if ($env:OS) { $env:OS } else { 'win32' }
|
|
11
|
+
$Arch = if ($env:ARCH) { $env:ARCH } else { 'x64' }
|
|
12
|
+
$ReleaseRepo = if ($env:RUNTIME_RELEASE_REPOSITORY) { $env:RUNTIME_RELEASE_REPOSITORY } else { 'tribgames/mixdog' }
|
|
13
|
+
$PgVer = '16.4'
|
|
14
|
+
$PgvectorVer = '0.8.2'
|
|
15
|
+
|
|
16
|
+
$Asset = "mixdog-runtime-${Os}-${Arch}-pg${PgVer}-pgvector${PgvectorVer}.tar.gz"
|
|
17
|
+
$Url = "https://github.com/${ReleaseRepo}/releases/download/${Tag}/${Asset}"
|
|
18
|
+
|
|
19
|
+
$Work = New-Item -ItemType Directory -Force -Path "$env:TEMP\smoke-$([guid]::NewGuid().ToString('N'))" | Select-Object -ExpandProperty FullName
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
Write-Host "==> Negative-path smoke for ${Os}-${Arch} from $Url"
|
|
23
|
+
|
|
24
|
+
Write-Host "==> Test 1: HEAD reaches release asset"
|
|
25
|
+
$resp = Invoke-WebRequest -Uri $Url -Method Head -UseBasicParsing -ErrorAction Stop
|
|
26
|
+
Write-Host " HTTP $($resp.StatusCode)"
|
|
27
|
+
|
|
28
|
+
Write-Host "==> Test 2: full download + sha256"
|
|
29
|
+
$TarPath = Join-Path $Work $Asset
|
|
30
|
+
Invoke-WebRequest -Uri $Url -OutFile $TarPath -UseBasicParsing
|
|
31
|
+
$sha = (Get-FileHash -Algorithm SHA256 $TarPath).Hash.ToLower()
|
|
32
|
+
Write-Host " sha256=$sha"
|
|
33
|
+
|
|
34
|
+
Write-Host "==> Test 3: corrupt tarball — flip a byte and ensure tar errors"
|
|
35
|
+
$CorruptPath = "$Work\corrupt.tar.gz"
|
|
36
|
+
Copy-Item $TarPath $CorruptPath
|
|
37
|
+
$bytes = [byte[]]::new(1); $rand = [System.Random]::new(); $rand.NextBytes($bytes)
|
|
38
|
+
$stream = [System.IO.File]::OpenWrite($CorruptPath)
|
|
39
|
+
$stream.Position = 102400
|
|
40
|
+
$stream.Write($bytes, 0, 1)
|
|
41
|
+
$stream.Close()
|
|
42
|
+
$CorruptDir = "$Work\corrupt"
|
|
43
|
+
New-Item -ItemType Directory -Force -Path $CorruptDir | Out-Null
|
|
44
|
+
$TarProc = Start-Process -FilePath tar -ArgumentList @('-xzf', $CorruptPath, '-C', $CorruptDir.Replace('\','/')) -Wait:$false -PassThru -WindowStyle Hidden
|
|
45
|
+
if (-not $TarProc.WaitForExit(15000)) {
|
|
46
|
+
Stop-Process -Id $TarProc.Id -Force -ErrorAction SilentlyContinue
|
|
47
|
+
Write-Host " PASS: corrupted tarball extraction timed out and was stopped"
|
|
48
|
+
} elseif ($TarProc.ExitCode -eq 0) {
|
|
49
|
+
Write-Host " WARN: corrupted tarball extracted without error (tar gzip recovery)"
|
|
50
|
+
} else {
|
|
51
|
+
Write-Host " PASS: corrupted tarball rejected by tar (exit $($TarProc.ExitCode))"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
Write-Host "==> Test 4: fresh-extract boot (extension load + distance query)"
|
|
55
|
+
$FreshDir = "$Work\fresh"
|
|
56
|
+
New-Item -ItemType Directory -Force -Path $FreshDir | Out-Null
|
|
57
|
+
& tar -xzf $TarPath -C ($FreshDir.Replace('\','/'))
|
|
58
|
+
if ($LASTEXITCODE -ne 0) { throw "fresh extract failed" }
|
|
59
|
+
|
|
60
|
+
$PgBin = "$FreshDir\bin"
|
|
61
|
+
$Data = "$FreshDir\pgdata"
|
|
62
|
+
$Log = "$FreshDir\pg.log"
|
|
63
|
+
$Port = 55897
|
|
64
|
+
|
|
65
|
+
# Hostile env: minimal PATH (System32 only), no PGROOT/PGDATA
|
|
66
|
+
$SavedPath = $env:PATH
|
|
67
|
+
$SavedPgRoot = $env:PGROOT
|
|
68
|
+
$env:PATH = "$env:SystemRoot\System32;$env:SystemRoot"
|
|
69
|
+
$env:PGROOT = $null
|
|
70
|
+
$env:PGDATA = $null
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
& "$PgBin\postgres.exe" --version
|
|
74
|
+
if ($LASTEXITCODE -ne 0) { throw "FAIL: postgres --version" }
|
|
75
|
+
& "$PgBin\initdb.exe" -D $Data --username=postgres --auth-local=trust --no-locale -E UTF8 | Out-Null
|
|
76
|
+
if ($LASTEXITCODE -ne 0) { throw "FAIL: initdb" }
|
|
77
|
+
& "$PgBin\pg_ctl.exe" -D $Data -o "-p $Port -h 127.0.0.1" -l $Log -w start
|
|
78
|
+
if ($LASTEXITCODE -ne 0) { Get-Content $Log | Select-Object -Last 30; throw "FAIL: pg_ctl start" }
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
& "$PgBin\psql.exe" -h 127.0.0.1 -p $Port -U postgres -d postgres -c "CREATE EXTENSION vector;" | Out-Null
|
|
82
|
+
if ($LASTEXITCODE -ne 0) { throw "FAIL: CREATE EXTENSION" }
|
|
83
|
+
$ExtV = & "$PgBin\psql.exe" -h 127.0.0.1 -p $Port -U postgres -d postgres -tAc "SELECT extversion FROM pg_extension WHERE extname='vector';"
|
|
84
|
+
if ($ExtV.Trim() -ne $PgvectorVer) { throw "FAIL: extversion='$ExtV' expected='$PgvectorVer'" }
|
|
85
|
+
Write-Host " PASS: fresh-extract boot + vector extension"
|
|
86
|
+
} finally {
|
|
87
|
+
& "$PgBin\pg_ctl.exe" -D $Data -m fast stop 2>$null | Out-Null
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
Write-Host "==> Test 5: second initdb on initialized dir refused"
|
|
91
|
+
$rc = (Start-Process -FilePath "$PgBin\initdb.exe" -ArgumentList "-D",$Data,"-U","postgres" -Wait -PassThru -NoNewWindow).ExitCode
|
|
92
|
+
if ($rc -eq 0) {
|
|
93
|
+
Write-Host " WARN: second initdb succeeded (unexpected)"
|
|
94
|
+
} else {
|
|
95
|
+
Write-Host " PASS: second initdb refused (exit $rc)"
|
|
96
|
+
}
|
|
97
|
+
} finally {
|
|
98
|
+
$env:PATH = $SavedPath
|
|
99
|
+
$env:PGROOT = $SavedPgRoot
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
Write-Host "==> All negative-path smokes: PASS"
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
Remove-Item -Recurse -Force $Work -ErrorAction SilentlyContinue
|
|
106
|
+
}
|
package/scripts/tool-smoke.mjs
CHANGED
|
@@ -345,6 +345,23 @@ const grepOut = await executeBuiltinTool('grep', {
|
|
|
345
345
|
}, root);
|
|
346
346
|
assertOk('grep', grepOut, /smoke\.mjs/);
|
|
347
347
|
|
|
348
|
+
const grepBracketPathOut = await executeBuiltinTool('grep', {
|
|
349
|
+
pattern: 'tool-smoke',
|
|
350
|
+
path: '[]',
|
|
351
|
+
glob: '*.mjs',
|
|
352
|
+
head_limit: 5,
|
|
353
|
+
}, root);
|
|
354
|
+
assertOk('grep path [] coerces to cwd', grepBracketPathOut, /tool-smoke\.mjs/);
|
|
355
|
+
|
|
356
|
+
const grepRedirectOut = await executeBuiltinTool('grep', {
|
|
357
|
+
pattern: 'assertOk',
|
|
358
|
+
path: 'bogus/wrong/prefix/scripts/tool-smoke.mjs',
|
|
359
|
+
head_limit: 3,
|
|
360
|
+
}, root);
|
|
361
|
+
if (!/^\[redirected from/.test(grepRedirectOut) || !/assertOk/.test(grepRedirectOut)) {
|
|
362
|
+
throw new Error(`grep ENOENT should auto-redirect on unique suffix hit:\n${grepRedirectOut.slice(0, 800)}`);
|
|
363
|
+
}
|
|
364
|
+
|
|
348
365
|
const redundantAllFilesGlobGrepOut = await executeBuiltinTool('grep', {
|
|
349
366
|
pattern: 'standalone mixdog CLI/TUI coding agent',
|
|
350
367
|
glob: '**/*',
|
|
@@ -367,6 +384,98 @@ const explicitSrcGlobOut = await executeBuiltinTool('glob', {
|
|
|
367
384
|
}, root);
|
|
368
385
|
assertOk('glob explicit src', explicitSrcGlobOut, /src[\\/].*engine\.mjs/i);
|
|
369
386
|
|
|
387
|
+
const globPathOnlyOut = await executeBuiltinTool('glob', {
|
|
388
|
+
path: 'scripts',
|
|
389
|
+
head_limit: 8,
|
|
390
|
+
}, root);
|
|
391
|
+
assertOk('glob path-only default *', globPathOnlyOut, /tool-smoke\.mjs/i);
|
|
392
|
+
|
|
393
|
+
const grepNoPatternGlobOut = await executeBuiltinTool('grep', {
|
|
394
|
+
path: 'scripts',
|
|
395
|
+
glob: 'tool-smoke.mjs',
|
|
396
|
+
head_limit: 5,
|
|
397
|
+
}, root);
|
|
398
|
+
assertOk('grep without pattern routes to glob', grepNoPatternGlobOut, /tool-smoke\.mjs/i);
|
|
399
|
+
|
|
400
|
+
const grepManyPatterns = [
|
|
401
|
+
...Array.from({ length: 20 }, (_, i) => `__tool_smoke_miss_${i}__`),
|
|
402
|
+
'tool-smoke',
|
|
403
|
+
];
|
|
404
|
+
const grepManyPatternsOut = await executeBuiltinTool('grep', {
|
|
405
|
+
pattern: grepManyPatterns,
|
|
406
|
+
path: 'scripts',
|
|
407
|
+
glob: '*.mjs',
|
|
408
|
+
head_limit: 5,
|
|
409
|
+
}, root);
|
|
410
|
+
if (/exceeds the 20-pattern cap/i.test(String(grepManyPatternsOut))) {
|
|
411
|
+
throw new Error(`grep should chunk >20 patterns instead of error:\n${grepManyPatternsOut.slice(0, 400)}`);
|
|
412
|
+
}
|
|
413
|
+
assertOk('grep >20 pattern chunk merge', grepManyPatternsOut, /tool-smoke\.mjs/i);
|
|
414
|
+
if (!/\[pattern set split into 2 chunks\]/.test(String(grepManyPatternsOut))) {
|
|
415
|
+
throw new Error(`grep >20 patterns should emit chunk advisory:\n${grepManyPatternsOut.slice(0, 400)}`);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function grepCountTotalMatches(body) {
|
|
419
|
+
const m = String(body).match(/\[total (\d+) match/i);
|
|
420
|
+
return m ? Number(m[1]) : null;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const grepCountSingleOut = await executeBuiltinTool('grep', {
|
|
424
|
+
pattern: 'assertOk',
|
|
425
|
+
path: 'scripts/tool-smoke.mjs',
|
|
426
|
+
output_mode: 'count',
|
|
427
|
+
}, root);
|
|
428
|
+
const singleCountTotal = grepCountTotalMatches(grepCountSingleOut);
|
|
429
|
+
if (singleCountTotal == null || singleCountTotal < 1) {
|
|
430
|
+
throw new Error(`grep count baseline failed:\n${grepCountSingleOut.slice(0, 400)}`);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const grepCountOverlapPatterns = [
|
|
434
|
+
...Array.from({ length: 19 }, (_, i) => `__count_overlap_a_${i}__`),
|
|
435
|
+
'assertOk',
|
|
436
|
+
...Array.from({ length: 19 }, (_, i) => `__count_overlap_b_${i}__`),
|
|
437
|
+
'assertOk',
|
|
438
|
+
];
|
|
439
|
+
const grepCountOverlapOut = await executeBuiltinTool('grep', {
|
|
440
|
+
pattern: grepCountOverlapPatterns,
|
|
441
|
+
path: 'scripts/tool-smoke.mjs',
|
|
442
|
+
output_mode: 'count',
|
|
443
|
+
}, root);
|
|
444
|
+
const overlapCountTotal = grepCountTotalMatches(grepCountOverlapOut);
|
|
445
|
+
if (overlapCountTotal !== singleCountTotal) {
|
|
446
|
+
throw new Error(
|
|
447
|
+
`chunked count must not double-count overlapping lines (single=${singleCountTotal} overlap=${overlapCountTotal}):\n${grepCountOverlapOut.slice(0, 600)}`,
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const grepChunkContextPatterns = [
|
|
452
|
+
...Array.from({ length: 20 }, (_, i) => `__ctx_chunk_miss_${i}__`),
|
|
453
|
+
'grepCountTotalMatches',
|
|
454
|
+
];
|
|
455
|
+
const grepChunkContextOut = await executeBuiltinTool('grep', {
|
|
456
|
+
pattern: grepChunkContextPatterns,
|
|
457
|
+
path: 'scripts/tool-smoke.mjs',
|
|
458
|
+
'-C': 1,
|
|
459
|
+
head_limit: 30,
|
|
460
|
+
}, root);
|
|
461
|
+
if (!/\[pattern set split into 2 chunks\]/.test(String(grepChunkContextOut))) {
|
|
462
|
+
throw new Error(`chunked -C grep should split patterns:\n${grepChunkContextOut.slice(0, 500)}`);
|
|
463
|
+
}
|
|
464
|
+
if (!/tool-smoke\.mjs:\d+:/.test(String(grepChunkContextOut))) {
|
|
465
|
+
throw new Error(`chunked -C must emit path-prefixed match lines:\n${grepChunkContextOut.slice(0, 800)}`);
|
|
466
|
+
}
|
|
467
|
+
if (!/tool-smoke\.mjs-\d+-/.test(String(grepChunkContextOut))) {
|
|
468
|
+
throw new Error(`chunked -C must keep path-prefixed context lines:\n${grepChunkContextOut.slice(0, 800)}`);
|
|
469
|
+
}
|
|
470
|
+
const ctxBodyLines = String(grepChunkContextOut).split('\n').filter((l) => l && !/^\[/.test(l) && !/^\(no matches\)/.test(l));
|
|
471
|
+
const orphanLineOnlyContext = ctxBodyLines.some((l) => /^\d+-/.test(l));
|
|
472
|
+
if (orphanLineOnlyContext) {
|
|
473
|
+
throw new Error(`chunked -C must not leave line-only context orphans:\n${grepChunkContextOut.slice(0, 800)}`);
|
|
474
|
+
}
|
|
475
|
+
if (!/function grepCountTotalMatches/.test(String(grepChunkContextOut))) {
|
|
476
|
+
throw new Error(`chunked -C should include match span:\n${grepChunkContextOut.slice(0, 800)}`);
|
|
477
|
+
}
|
|
478
|
+
|
|
370
479
|
const findOut = await executeBuiltinTool('find', {
|
|
371
480
|
query: 'tool smoke',
|
|
372
481
|
path: '.',
|
|
@@ -818,6 +818,10 @@ export async function createMixdogSessionRuntime({
|
|
|
818
818
|
}
|
|
819
819
|
|
|
820
820
|
function skillToolContent(name) {
|
|
821
|
+
if (typeof contextMod.isSkillDisabled === 'function' && contextMod.isSkillDisabled(name)) {
|
|
822
|
+
const label = String(name || '').trim() || 'skill';
|
|
823
|
+
return `Error: skill "${label}" is disabled`;
|
|
824
|
+
}
|
|
821
825
|
const skill = skillContent(name);
|
|
822
826
|
// Return the general tool envelope so the main/Lead session behaves the
|
|
823
827
|
// same as agent-loop sessions: the model-visible tool_result is the short
|
|
@@ -1596,6 +1600,9 @@ export async function createMixdogSessionRuntime({
|
|
|
1596
1600
|
// Channels are opt-in: only boot the worker when this session started in (or
|
|
1597
1601
|
// was toggled into) remote mode. Non-remote sessions never contend for the
|
|
1598
1602
|
// channel; see startRemote()/stopRemote() and the `/remote` toggle.
|
|
1603
|
+
// `remote.autoStart` in mixdog-config.json makes every session claim remote
|
|
1604
|
+
// at boot (same force-takeover semantics as `mixdog --remote` / `/remote`).
|
|
1605
|
+
if (!remoteEnabled && config?.remote?.autoStart === true) remoteEnabled = true;
|
|
1599
1606
|
if (remoteEnabled) startRemote();
|
|
1600
1607
|
|
|
1601
1608
|
function contextStatusCacheKeyFor({ messages, tools }) {
|
|
@@ -1657,6 +1664,7 @@ export async function createMixdogSessionRuntime({
|
|
|
1657
1664
|
getRoute: () => route,
|
|
1658
1665
|
getSession: () => session,
|
|
1659
1666
|
getRemoteEnabled: () => remoteEnabled,
|
|
1667
|
+
adoptConfig,
|
|
1660
1668
|
saveConfigAndAdopt,
|
|
1661
1669
|
scheduleBackendSave,
|
|
1662
1670
|
cfgMod,
|
|
@@ -6,29 +6,27 @@ kind: retrieval
|
|
|
6
6
|
|
|
7
7
|
# Role: explorer
|
|
8
8
|
|
|
9
|
-
You are a one-shot locator, not a researcher. You find WHERE, never WHY
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
You are a one-shot locator, not a researcher. You find WHERE, never WHY —
|
|
10
|
+
no understanding code, explaining behaviour, or tracing logic. Coordinates
|
|
11
|
+
are the entire deliverable.
|
|
12
12
|
|
|
13
|
-
Default shape: ONE
|
|
14
|
-
a single `grep`
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
turn means turn 1 under-batched.
|
|
13
|
+
Default shape: ONE maximal-batch turn, then the answer. Fire everything at
|
|
14
|
+
once: a single `grep` whose pattern[] carries ALL literal strings/errors/
|
|
15
|
+
identifier guesses — synonyms, casings, variants — plus `code_graph`/`find`/
|
|
16
|
+
`glob` in the SAME turn when useful. Width is free, turns are not.
|
|
18
17
|
|
|
19
|
-
Answer straight from search output:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
`dist`/generated hits are leads, not answers: trace them back to source.
|
|
18
|
+
Answer straight from search output: hits already carry `path:line` — never
|
|
19
|
+
spend a turn on `read` to confirm or polish an anchor. `dist`/generated
|
|
20
|
+
hits are leads, not answers: trace them to source.
|
|
23
21
|
|
|
24
|
-
Budget: 5
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
best-so-far
|
|
22
|
+
Budget: 5 turns of maximal batches. Credible `path:line` on screen means the
|
|
23
|
+
search is OVER — answer in that turn; no verifying or collecting extras;
|
|
24
|
+
mark weak anchors `?`. Turns 2-5 exist ONLY for a zero-anchor turn 1: derive
|
|
25
|
+
new tokens from returned paths/names or widen scope, packing ALL candidates
|
|
26
|
+
into the next batch. Concept queries: grep implementation vocabulary
|
|
27
|
+
(file/dir names, symbol fragments, config keys). `EXPLORATION_FAILED` only
|
|
28
|
+
after all 5 turns found zero anchors — giving up early is as wrong as
|
|
29
|
+
over-searching; at turn 5 answer best-so-far.
|
|
32
30
|
|
|
33
31
|
Answer format, nothing else:
|
|
34
32
|
- up to 5 lines: `path:line — symbol/name — short reason` (append `?` if weak)
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
# Channels
|
|
2
|
-
|
|
3
|
-
- Channel features are handled by the runtime.
|
|
1
|
+
# Channels
|
|
2
|
+
|
|
3
|
+
- Channel features are handled by the runtime.
|
|
@@ -81,6 +81,16 @@ export function normalizeProfileConfig(value = {}) {
|
|
|
81
81
|
return { title, language };
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
/** Persisted `skills.disabled` name list (deduped, trimmed, sorted). */
|
|
85
|
+
export function normalizeSkillsConfig(value = {}) {
|
|
86
|
+
const raw = value && typeof value === 'object' ? value : {};
|
|
87
|
+
const disabled = Array.isArray(raw.disabled)
|
|
88
|
+
? [...new Set(raw.disabled.map((n) => String(n).trim()).filter(Boolean))]
|
|
89
|
+
: [];
|
|
90
|
+
disabled.sort((a, b) => a.localeCompare(b));
|
|
91
|
+
return { disabled };
|
|
92
|
+
}
|
|
93
|
+
|
|
84
94
|
// Look up the catalog entry for a stored language id (defaults to 'system').
|
|
85
95
|
export function profileLanguageEntry(languageId) {
|
|
86
96
|
const id = String(languageId || 'system');
|
|
@@ -435,6 +445,7 @@ export function loadConfig(options = {}) {
|
|
|
435
445
|
workflow: raw.workflow && typeof raw.workflow === 'object' ? { active: String(raw.workflow.active || 'default') } : { active: 'default' },
|
|
436
446
|
agentMaintenance: { enabled: true, interval: '1h', ...raw.agentMaintenance },
|
|
437
447
|
profile: normalizeProfileConfig(raw.profile),
|
|
448
|
+
skills: normalizeSkillsConfig(raw.skills),
|
|
438
449
|
// No idleMs default here: absent idleMs means "provider default"
|
|
439
450
|
// (config-helpers normalizeAutoClearConfig custom:false path).
|
|
440
451
|
// Injecting a fixed 1h would mark every config custom:true and
|
|
@@ -467,6 +478,7 @@ export function loadConfig(options = {}) {
|
|
|
467
478
|
workflow: { active: 'default' },
|
|
468
479
|
agentMaintenance: { enabled: true, interval: '1h' },
|
|
469
480
|
profile: normalizeProfileConfig(null),
|
|
481
|
+
skills: normalizeSkillsConfig(null),
|
|
470
482
|
autoClear: { enabled: true },
|
|
471
483
|
compaction: {},
|
|
472
484
|
trajectory: { enabled: true },
|
|
@@ -493,6 +505,23 @@ export function loadConfig(options = {}) {
|
|
|
493
505
|
* via persistAgentConfig((current) => ({ ...current, <field> })) so a
|
|
494
506
|
* concurrent instance's edits are not reverted.
|
|
495
507
|
*/
|
|
508
|
+
/** In-lock patch of `skills.disabled` only (avoids whole-config lost-update). */
|
|
509
|
+
export function patchSkillsDisabled(disabledNames) {
|
|
510
|
+
const names = disabledNames instanceof Set
|
|
511
|
+
? [...disabledNames]
|
|
512
|
+
: (Array.isArray(disabledNames) ? disabledNames : []);
|
|
513
|
+
const nextSkills = normalizeSkillsConfig({ disabled: names });
|
|
514
|
+
persistAgentConfig((current) => {
|
|
515
|
+
const cur = { ...current };
|
|
516
|
+
const target = (cur.agent && cur.agent.providers)
|
|
517
|
+
? (cur.agent = { ...cur.agent })
|
|
518
|
+
: cur;
|
|
519
|
+
target.skills = nextSkills;
|
|
520
|
+
return cur;
|
|
521
|
+
});
|
|
522
|
+
return nextSkills;
|
|
523
|
+
}
|
|
524
|
+
|
|
496
525
|
export function saveConfig(config) {
|
|
497
526
|
// Strip ephemeral defaults from providers but preserve any unknown
|
|
498
527
|
// per-provider subkey so future schema additions round-trip through the
|
|
@@ -544,6 +573,7 @@ export function saveConfig(config) {
|
|
|
544
573
|
workflow: config.workflow || { active: 'default' },
|
|
545
574
|
agentMaintenance: config.agentMaintenance || {},
|
|
546
575
|
profile: normalizeProfileConfig(config.profile),
|
|
576
|
+
skills: normalizeSkillsConfig(config.skills),
|
|
547
577
|
autoClear: config.autoClear || {},
|
|
548
578
|
compaction: config.compaction || {},
|
|
549
579
|
trajectory: config.trajectory || {},
|