sneakoscope 2.0.1 → 2.0.2
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 +26 -5
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/.sks-build-stamp.json +4 -4
- package/dist/bin/sks.js +1 -1
- package/dist/build-manifest.json +15 -8
- package/dist/cli/command-registry.js +2 -0
- package/dist/commands/doctor.js +29 -3
- package/dist/core/agents/agent-command-surface.js +13 -3
- package/dist/core/agents/agent-orchestrator.js +22 -0
- package/dist/core/agents/agent-output-validator.js +2 -1
- package/dist/core/agents/agent-patch-schema.js +2 -1
- package/dist/core/agents/agent-roster.js +1 -1
- package/dist/core/agents/agent-runner-ollama.js +411 -0
- package/dist/core/agents/agent-schema.js +1 -1
- package/dist/core/agents/intelligent-work-graph.js +45 -3
- package/dist/core/agents/native-cli-session-swarm.js +8 -1
- package/dist/core/agents/native-cli-worker.js +1 -1
- package/dist/core/agents/native-worker-backend-router.js +44 -2
- package/dist/core/agents/ollama-worker-config.js +118 -0
- package/dist/core/auto-review.js +39 -6
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +42 -3
- package/dist/core/commands/basic-cli.js +36 -1
- package/dist/core/commands/local-model-command.js +105 -0
- package/dist/core/commands/mad-sks-command.js +58 -9
- package/dist/core/commands/run-command.js +29 -1
- package/dist/core/commands/team-command.js +31 -2
- package/dist/core/doctor/doctor-readiness-matrix.js +4 -0
- package/dist/core/feature-fixtures.js +1 -0
- package/dist/core/fsx.js +1 -1
- package/dist/core/hooks-runtime.js +1 -1
- package/dist/core/init.js +2 -0
- package/dist/core/provider/provider-context.js +72 -9
- package/dist/core/retention.js +11 -0
- package/dist/core/routes.js +21 -1
- package/dist/core/team-live.js +7 -1
- package/dist/core/update-check.js +156 -1
- package/dist/core/verification/verification-worker-pool.js +12 -0
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-worker-pane-manager.js +19 -2
- package/dist/scripts/agent-ast-aware-work-graph-check.js +1 -1
- package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +12 -2
- package/dist/scripts/mad-sks-app-ui-no-mutation-check.js +92 -0
- package/dist/scripts/mad-sks-zellij-default-pane-worker-check.js +37 -0
- package/dist/scripts/mad-sks-zellij-launch-check.js +2 -1
- package/dist/scripts/provider-context-config-toml-check.js +63 -0
- package/dist/scripts/release-gate-existence-audit.js +4 -0
- package/dist/scripts/runtime-no-mjs-scripts-check.js +3 -2
- package/dist/scripts/zellij-worker-pane-manager-check.js +3 -0
- package/dist/scripts/zellij-worker-pane-manager-single-owner-check.js +39 -0
- package/package.json +7 -3
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { ensureDir, exists, nowIso, readJson, writeJsonAtomic } from '../fsx.js';
|
|
4
|
+
export const LOCAL_MODEL_CONFIG_SCHEMA = 'sks.local-model-config.v1';
|
|
5
|
+
export const OLLAMA_WORKER_CONFIG_SCHEMA = 'sks.ollama-worker-config.v1';
|
|
6
|
+
export const DEFAULT_OLLAMA_CODER_MODEL = 'rafw007/qwen36-a3b-claude-coder:q4_K_M';
|
|
7
|
+
export const DEFAULT_OLLAMA_BASE_URL = 'http://127.0.0.1:11434';
|
|
8
|
+
export const DEFAULT_OLLAMA_KEEP_ALIVE = '30m';
|
|
9
|
+
export const DEFAULT_OLLAMA_TIMEOUT_MS = 120_000;
|
|
10
|
+
export const DEFAULT_OLLAMA_THINK = false;
|
|
11
|
+
export function localModelConfigPath() {
|
|
12
|
+
return process.env.SKS_LOCAL_MODEL_CONFIG
|
|
13
|
+
? path.resolve(process.env.SKS_LOCAL_MODEL_CONFIG)
|
|
14
|
+
: path.join(os.homedir(), '.sneakoscope', 'local-model.json');
|
|
15
|
+
}
|
|
16
|
+
export async function readLocalModelConfig() {
|
|
17
|
+
const raw = await readJson(localModelConfigPath(), null);
|
|
18
|
+
return normalizeLocalModelConfig(raw || {});
|
|
19
|
+
}
|
|
20
|
+
export async function writeLocalModelConfig(patch) {
|
|
21
|
+
const current = await readLocalModelConfig();
|
|
22
|
+
const next = normalizeLocalModelConfig({ ...current, ...patch, updated_at: nowIso() });
|
|
23
|
+
await ensureDir(path.dirname(localModelConfigPath()));
|
|
24
|
+
await writeJsonAtomic(localModelConfigPath(), next);
|
|
25
|
+
return next;
|
|
26
|
+
}
|
|
27
|
+
export async function resolveOllamaWorkerConfig(input = {}) {
|
|
28
|
+
const configExists = await exists(localModelConfigPath());
|
|
29
|
+
const stored = await readLocalModelConfig();
|
|
30
|
+
const explicitDisable = boolEnv(process.env.SKS_OLLAMA_WORKERS) === false;
|
|
31
|
+
const explicitEnable = boolEnv(process.env.SKS_OLLAMA_WORKERS) === true || input.ollamaEnabled === true || input.backend === 'ollama';
|
|
32
|
+
const enabled = explicitDisable ? false : explicitEnable || stored.enabled === true;
|
|
33
|
+
const model = firstText(process.env.SKS_OLLAMA_MODEL, input.model, stored.model, DEFAULT_OLLAMA_CODER_MODEL);
|
|
34
|
+
const baseUrl = trimTrailingSlash(firstText(process.env.SKS_OLLAMA_BASE_URL, input.baseUrl, stored.base_url, DEFAULT_OLLAMA_BASE_URL));
|
|
35
|
+
const keepAlive = firstText(process.env.SKS_OLLAMA_KEEP_ALIVE, input.keepAlive, stored.keep_alive, DEFAULT_OLLAMA_KEEP_ALIVE);
|
|
36
|
+
const timeoutMs = positiveNumber(process.env.SKS_OLLAMA_TIMEOUT_MS, input.timeoutMs, stored.timeout_ms, DEFAULT_OLLAMA_TIMEOUT_MS);
|
|
37
|
+
const temperature = finiteNumber(process.env.SKS_OLLAMA_TEMPERATURE, input.temperature, stored.temperature, 0.1);
|
|
38
|
+
const think = boolEnv(process.env.SKS_OLLAMA_THINK) ?? input.think ?? stored.think ?? DEFAULT_OLLAMA_THINK;
|
|
39
|
+
const blockers = [
|
|
40
|
+
...(enabled ? [] : ['ollama_workers_disabled']),
|
|
41
|
+
...(!model ? ['ollama_model_missing'] : []),
|
|
42
|
+
...(!baseUrl ? ['ollama_base_url_missing'] : [])
|
|
43
|
+
];
|
|
44
|
+
return {
|
|
45
|
+
schema: OLLAMA_WORKER_CONFIG_SCHEMA,
|
|
46
|
+
ok: blockers.length === 0,
|
|
47
|
+
enabled,
|
|
48
|
+
provider: 'ollama',
|
|
49
|
+
model,
|
|
50
|
+
base_url: baseUrl,
|
|
51
|
+
keep_alive: keepAlive,
|
|
52
|
+
timeout_ms: timeoutMs,
|
|
53
|
+
temperature,
|
|
54
|
+
think,
|
|
55
|
+
config_path: localModelConfigPath(),
|
|
56
|
+
explicit_disable: explicitDisable,
|
|
57
|
+
explicit_enable: explicitEnable,
|
|
58
|
+
blockers
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export function normalizeLocalModelConfig(raw = {}) {
|
|
62
|
+
return {
|
|
63
|
+
schema: LOCAL_MODEL_CONFIG_SCHEMA,
|
|
64
|
+
...(raw.generated_at ? { generated_at: String(raw.generated_at) } : { generated_at: nowIso() }),
|
|
65
|
+
...(raw.updated_at ? { updated_at: String(raw.updated_at) } : {}),
|
|
66
|
+
enabled: raw.enabled === true,
|
|
67
|
+
provider: 'ollama',
|
|
68
|
+
model: firstText(raw.model, DEFAULT_OLLAMA_CODER_MODEL),
|
|
69
|
+
base_url: trimTrailingSlash(firstText(raw.base_url, raw.baseUrl, DEFAULT_OLLAMA_BASE_URL)),
|
|
70
|
+
keep_alive: firstText(raw.keep_alive, raw.keepAlive, DEFAULT_OLLAMA_KEEP_ALIVE),
|
|
71
|
+
timeout_ms: positiveNumber(raw.timeout_ms, raw.timeoutMs, DEFAULT_OLLAMA_TIMEOUT_MS),
|
|
72
|
+
temperature: finiteNumber(raw.temperature, 0.1),
|
|
73
|
+
think: typeof raw.think === 'boolean' ? raw.think : DEFAULT_OLLAMA_THINK,
|
|
74
|
+
policy: {
|
|
75
|
+
worker_only: true,
|
|
76
|
+
no_strategy_planning_design: true,
|
|
77
|
+
allowed_work: ['simple_code_patch_envelopes', 'read_only_collection']
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export function boolEnv(value) {
|
|
82
|
+
const text = String(value ?? '').trim().toLowerCase();
|
|
83
|
+
if (!text)
|
|
84
|
+
return null;
|
|
85
|
+
if (['1', 'true', 'on', 'yes', 'enable', 'enabled'].includes(text))
|
|
86
|
+
return true;
|
|
87
|
+
if (['0', 'false', 'off', 'no', 'disable', 'disabled'].includes(text))
|
|
88
|
+
return false;
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
function firstText(...values) {
|
|
92
|
+
for (const value of values) {
|
|
93
|
+
const text = String(value ?? '').trim();
|
|
94
|
+
if (text)
|
|
95
|
+
return text;
|
|
96
|
+
}
|
|
97
|
+
return '';
|
|
98
|
+
}
|
|
99
|
+
function trimTrailingSlash(value) {
|
|
100
|
+
return value.replace(/\/+$/, '');
|
|
101
|
+
}
|
|
102
|
+
function positiveNumber(...values) {
|
|
103
|
+
for (const value of values) {
|
|
104
|
+
const n = Number(value);
|
|
105
|
+
if (Number.isFinite(n) && n > 0)
|
|
106
|
+
return Math.floor(n);
|
|
107
|
+
}
|
|
108
|
+
return DEFAULT_OLLAMA_TIMEOUT_MS;
|
|
109
|
+
}
|
|
110
|
+
function finiteNumber(...values) {
|
|
111
|
+
for (const value of values) {
|
|
112
|
+
const n = Number(value);
|
|
113
|
+
if (Number.isFinite(n))
|
|
114
|
+
return n;
|
|
115
|
+
}
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=ollama-worker-config.js.map
|
package/dist/core/auto-review.js
CHANGED
|
@@ -108,7 +108,7 @@ export const SKS_CONFIG_PROFILES = [
|
|
|
108
108
|
{ name: 'sks-research-xhigh', stripTable: true, block: sksProfileFileBlock({ effort: 'xhigh' }) },
|
|
109
109
|
{ name: 'sks-research', stripTable: true, block: sksProfileFileBlock({ effort: 'xhigh', approvalPolicy: 'never' }) },
|
|
110
110
|
{ name: 'sks-team', stripTable: true, block: sksProfileFileBlock({ effort: 'medium' }) },
|
|
111
|
-
{ name: MAD_HIGH_PROFILE, stripTable: true, block: sksProfileFileBlock({ effort: '
|
|
111
|
+
{ name: MAD_HIGH_PROFILE, stripTable: true, block: sksProfileFileBlock({ effort: 'xhigh', approvalPolicy: 'never', sandboxMode: 'danger-full-access', reviewer: AUTO_REVIEW_REVIEWER }) },
|
|
112
112
|
{ name: 'sks-default', stripTable: true, block: sksProfileFileBlock({ effort: 'high' }) }
|
|
113
113
|
];
|
|
114
114
|
function sksProfileFileBlock(opts = {}) {
|
|
@@ -146,7 +146,32 @@ export async function migrateSksProfilesToPerFile(opts = {}) {
|
|
|
146
146
|
tables_stripped: SKS_CONFIG_PROFILES.filter((profile) => profile.stripTable).map((profile) => profile.name)
|
|
147
147
|
};
|
|
148
148
|
}
|
|
149
|
-
export
|
|
149
|
+
export function buildMadHighLaunchProfileNoWrite(opts = {}) {
|
|
150
|
+
const env = opts.env || process.env;
|
|
151
|
+
const profileName = String(opts.profileName || MAD_HIGH_PROFILE);
|
|
152
|
+
return {
|
|
153
|
+
config_path: codexConfigPath(env),
|
|
154
|
+
profile_config_path: codexProfileConfigPath(profileName, env),
|
|
155
|
+
profile_name: profileName,
|
|
156
|
+
launch_args: [
|
|
157
|
+
'--sandbox',
|
|
158
|
+
'danger-full-access',
|
|
159
|
+
'--ask-for-approval',
|
|
160
|
+
'never',
|
|
161
|
+
'-c',
|
|
162
|
+
'service_tier=fast',
|
|
163
|
+
'-c',
|
|
164
|
+
'model_reasoning_effort=xhigh'
|
|
165
|
+
],
|
|
166
|
+
sandbox_mode: 'danger-full-access',
|
|
167
|
+
approval_policy: 'never',
|
|
168
|
+
model_reasoning_effort: 'xhigh',
|
|
169
|
+
service_tier: 'fast',
|
|
170
|
+
scope: 'explicit_launch_only',
|
|
171
|
+
writes_user_codex_config: false
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
export async function ensureMadHighProfileForSetupOrRepair(opts = {}) {
|
|
150
175
|
const configPath = opts.configPath || codexConfigPath(opts.env || process.env);
|
|
151
176
|
const env = opts.env || process.env;
|
|
152
177
|
await ensureDir(path.dirname(configPath));
|
|
@@ -160,7 +185,7 @@ export async function enableMadHighProfile(opts = {}) {
|
|
|
160
185
|
// selectors so Codex stops warning about the legacy config profile on launch.
|
|
161
186
|
await migrateSksProfilesToPerFile({ configPath, env });
|
|
162
187
|
await writeProfileConfig(configPath, MAD_HIGH_PROFILE, profileConfigBlock({
|
|
163
|
-
effort: '
|
|
188
|
+
effort: 'xhigh',
|
|
164
189
|
approvalPolicy: 'never',
|
|
165
190
|
sandboxMode: 'danger-full-access'
|
|
166
191
|
}));
|
|
@@ -168,14 +193,22 @@ export async function enableMadHighProfile(opts = {}) {
|
|
|
168
193
|
config_path: configPath,
|
|
169
194
|
profile_config_path: path.join(path.dirname(configPath), `${MAD_HIGH_PROFILE}.config.toml`),
|
|
170
195
|
profile_name: MAD_HIGH_PROFILE,
|
|
171
|
-
launch_args: ['--profile', MAD_HIGH_PROFILE, '--sandbox', 'danger-full-access', '--ask-for-approval', 'never', '-c', 'service_tier=fast'],
|
|
196
|
+
launch_args: ['--profile', MAD_HIGH_PROFILE, '--sandbox', 'danger-full-access', '--ask-for-approval', 'never', '-c', 'service_tier=fast', '-c', 'model_reasoning_effort=xhigh'],
|
|
172
197
|
sandbox_mode: 'danger-full-access',
|
|
173
198
|
approval_policy: 'never',
|
|
174
199
|
approvals_reviewer: AUTO_REVIEW_REVIEWER,
|
|
175
|
-
model_reasoning_effort: '
|
|
176
|
-
|
|
200
|
+
model_reasoning_effort: 'xhigh',
|
|
201
|
+
service_tier: 'fast',
|
|
202
|
+
scope: 'setup_or_repair_only',
|
|
203
|
+
writes_user_codex_config: true
|
|
177
204
|
};
|
|
178
205
|
}
|
|
206
|
+
export async function enableMadHighProfile(opts = {}) {
|
|
207
|
+
if (opts.allowUserConfigWrite !== true) {
|
|
208
|
+
throw new Error('enableMadHighProfile writes Codex user config; use buildMadHighLaunchProfileNoWrite for sks --mad');
|
|
209
|
+
}
|
|
210
|
+
return ensureMadHighProfileForSetupOrRepair(opts);
|
|
211
|
+
}
|
|
179
212
|
export function madHighProfileName() {
|
|
180
213
|
return MAD_HIGH_PROFILE;
|
|
181
214
|
}
|
|
@@ -16,6 +16,9 @@ export async function repairCodexAppFastUi(root = process.cwd(), input = {}) {
|
|
|
16
16
|
{ scope: 'codex_home', file: path.join(home, 'config.toml'), mode: 'sks_caused_host_owned_keys' }
|
|
17
17
|
];
|
|
18
18
|
const actions = [];
|
|
19
|
+
const detectedProjectLocalForbiddenKeys = [];
|
|
20
|
+
const unsafeReasons = [];
|
|
21
|
+
let detectedSksCausedMutation = false;
|
|
19
22
|
for (const candidate of candidates) {
|
|
20
23
|
const text = await readText(candidate.file, null);
|
|
21
24
|
if (text == null) {
|
|
@@ -25,8 +28,16 @@ export async function repairCodexAppFastUi(root = process.cwd(), input = {}) {
|
|
|
25
28
|
const repaired = candidate.mode === 'project_forbidden_keys'
|
|
26
29
|
? stripProjectLocalForbiddenKeys(text)
|
|
27
30
|
: stripSksCausedHostOwnedLines(text);
|
|
31
|
+
if (candidate.mode === 'project_forbidden_keys')
|
|
32
|
+
detectedProjectLocalForbiddenKeys.push(...repaired.removedKeys);
|
|
33
|
+
if (candidate.mode === 'sks_caused_host_owned_keys') {
|
|
34
|
+
const unsafe = detectUnsafeFastUiRepair(text);
|
|
35
|
+
unsafeReasons.push(...unsafe);
|
|
36
|
+
if (repaired.text !== text)
|
|
37
|
+
detectedSksCausedMutation = true;
|
|
38
|
+
}
|
|
28
39
|
if (repaired.text === text) {
|
|
29
|
-
actions.push({ scope: candidate.scope, file: displayPath(candidate.file), status: 'ok', changed: false, removed_keys: repaired.removedKeys });
|
|
40
|
+
actions.push({ scope: candidate.scope, file: displayPath(candidate.file), status: unsafeReasons.length && candidate.mode === 'sks_caused_host_owned_keys' ? 'requires_confirmation' : 'ok', changed: false, removed_keys: repaired.removedKeys });
|
|
30
41
|
continue;
|
|
31
42
|
}
|
|
32
43
|
const backupPath = `${candidate.file}.codex-app-ui-repair-${Date.now().toString(36)}.bak`;
|
|
@@ -49,9 +60,12 @@ export async function repairCodexAppFastUi(root = process.cwd(), input = {}) {
|
|
|
49
60
|
}
|
|
50
61
|
const after = await snapshotCodexAppUiState(resolvedRoot, { codexHome: home });
|
|
51
62
|
const changed = actions.some((action) => action.changed);
|
|
63
|
+
const requiresConfirmation = unsafeReasons.length > 0 && input.force !== true;
|
|
64
|
+
const safeAutoApply = changed && !requiresConfirmation;
|
|
52
65
|
const manual = changed && !input.apply;
|
|
53
66
|
const blockers = [
|
|
54
|
-
...(
|
|
67
|
+
...(requiresConfirmation ? ['codex_app_fast_ui_repair_requires_confirmation'] : []),
|
|
68
|
+
...(manual && !safeAutoApply ? ['codex_app_fast_ui_repair_requires_explicit_apply'] : []),
|
|
55
69
|
...(after.indicators.secret_leak_suspected ? ['codex_app_ui_repair_secret_leak_suspected'] : [])
|
|
56
70
|
];
|
|
57
71
|
const report = {
|
|
@@ -59,19 +73,44 @@ export async function repairCodexAppFastUi(root = process.cwd(), input = {}) {
|
|
|
59
73
|
generated_at: nowIso(),
|
|
60
74
|
ok: blockers.length === 0,
|
|
61
75
|
apply: input.apply === true,
|
|
76
|
+
safe_auto_apply: safeAutoApply,
|
|
77
|
+
requires_confirmation: requiresConfirmation,
|
|
78
|
+
detected_sks_caused_mutation: detectedSksCausedMutation,
|
|
79
|
+
detected_project_local_forbidden_keys: [...new Set(detectedProjectLocalForbiddenKeys)],
|
|
80
|
+
unsafe_repair_reasons: [...new Set(unsafeReasons)],
|
|
62
81
|
fast_selector: changed ? (input.apply ? 'repaired' : 'manual_action_required') : before.indicators.fast_selector === 'maybe_hidden_or_locked' ? 'manual_action_required' : 'ok',
|
|
63
82
|
provider_selector: 'ok',
|
|
64
83
|
host_owned_config: input.apply && changed ? 'repaired_with_backup' : changed ? 'preserved_until_explicit_apply' : 'preserved',
|
|
65
84
|
actions,
|
|
66
85
|
before_fast_selector: before.indicators.fast_selector,
|
|
67
86
|
after_fast_selector: after.indicators.fast_selector,
|
|
68
|
-
next_action: manual ? 'Run `sks doctor --fix --repair-codex-app-ui` after reviewing the repair plan.' : changed ? 'Restart Codex App if the selector was already hidden.' : 'No Codex App UI repair needed.',
|
|
87
|
+
next_action: requiresConfirmation ? 'Run `sks doctor --fix --repair-codex-app-ui` after reviewing the repair plan.' : manual && safeAutoApply ? 'Run `sks doctor --fix` to apply the safe Codex App UI repair.' : manual ? 'Run `sks doctor --fix --repair-codex-app-ui` after reviewing the repair plan.' : changed ? 'Restart Codex App if the selector was already hidden.' : 'No Codex App UI repair needed.',
|
|
69
88
|
blockers
|
|
70
89
|
};
|
|
71
90
|
if (input.reportPath)
|
|
72
91
|
await writeJsonAtomic(input.reportPath, report);
|
|
73
92
|
return report;
|
|
74
93
|
}
|
|
94
|
+
function detectUnsafeFastUiRepair(text) {
|
|
95
|
+
const reasons = [];
|
|
96
|
+
const lines = text.split(/\r?\n/);
|
|
97
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
98
|
+
const line = lines[i] || '';
|
|
99
|
+
const serviceTier = line.match(/^\s*service_tier\s*=\s*"(standard|flex)"\s*(?:#.*)?$/i)?.[1];
|
|
100
|
+
const sksMarked = SKS_CAUSED_RE.test(line) || SKS_CAUSED_RE.test(lines[i - 1] || '') || SKS_CAUSED_RE.test(lines[i + 1] || '');
|
|
101
|
+
if (serviceTier && !sksMarked)
|
|
102
|
+
reasons.push(`user_selected_service_tier_${serviceTier.toLowerCase()}`);
|
|
103
|
+
}
|
|
104
|
+
if (hasOddUnescapedQuotes(text))
|
|
105
|
+
reasons.push('unparseable_config_requires_manual_review');
|
|
106
|
+
return [...new Set(reasons)];
|
|
107
|
+
}
|
|
108
|
+
function hasOddUnescapedQuotes(text) {
|
|
109
|
+
return text.split(/\r?\n/).some((line) => {
|
|
110
|
+
const stripped = line.replace(/\\"/g, '');
|
|
111
|
+
return (stripped.match(/"/g) || []).length % 2 === 1;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
75
114
|
function stripProjectLocalForbiddenKeys(text) {
|
|
76
115
|
const forbidden = scanProjectLocalForbiddenKeys(text);
|
|
77
116
|
if (!forbidden.length)
|
|
@@ -10,7 +10,7 @@ import { buildFeatureRegistry, validateFeatureRegistry } from '../feature-regist
|
|
|
10
10
|
import { hooksExplainReport } from '../../cli/feature-commands.js';
|
|
11
11
|
import { writeSelftestRouteProof } from '../proof/selftest-proof-fixtures.js';
|
|
12
12
|
import { createMission } from '../mission.js';
|
|
13
|
-
import { formatSksUpdateCheckText, runSksUpdateCheck } from '../update-check.js';
|
|
13
|
+
import { formatSksUpdateCheckText, runSksUpdateCheck, runSksUpdateNow } from '../update-check.js';
|
|
14
14
|
export async function helpCommand(args = []) {
|
|
15
15
|
const topic = args[0];
|
|
16
16
|
if (topic)
|
|
@@ -109,6 +109,34 @@ export async function updateCheckCommand(args = []) {
|
|
|
109
109
|
return printJson(result);
|
|
110
110
|
console.log(`${sksTextLogo()}\n\n${formatSksUpdateCheckText(result)}`);
|
|
111
111
|
}
|
|
112
|
+
export async function updateCommand(sub = 'check', args = []) {
|
|
113
|
+
const action = String(sub || 'check').toLowerCase();
|
|
114
|
+
if (action === 'check' || action === 'status')
|
|
115
|
+
return updateCheckCommand(args);
|
|
116
|
+
if (action !== 'now') {
|
|
117
|
+
console.error('Usage: sks update check|now [--version <version>] [--json] [--dry-run]');
|
|
118
|
+
process.exitCode = 1;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const result = await runSksUpdateNow({
|
|
122
|
+
version: valueAfter(args, '--version') || valueAfter(args, '-v'),
|
|
123
|
+
dryRun: flag(args, '--dry-run'),
|
|
124
|
+
timeoutMs: 10 * 60 * 1000,
|
|
125
|
+
maxOutputBytes: 128 * 1024
|
|
126
|
+
});
|
|
127
|
+
if (flag(args, '--json'))
|
|
128
|
+
return printJson(result);
|
|
129
|
+
console.log(`${sksTextLogo()}\n`);
|
|
130
|
+
console.log(`SKS update ${result.status}`);
|
|
131
|
+
if (result.command)
|
|
132
|
+
console.log(`Command: ${result.command}`);
|
|
133
|
+
if (result.global_root)
|
|
134
|
+
console.log(`Global root: ${result.global_root}`);
|
|
135
|
+
if (result.error)
|
|
136
|
+
console.log(`Error: ${result.error}`);
|
|
137
|
+
if (!result.ok)
|
|
138
|
+
process.exitCode = 1;
|
|
139
|
+
}
|
|
112
140
|
export async function setupCommand(args = []) {
|
|
113
141
|
const root = await projectRoot();
|
|
114
142
|
const installScope = installScopeFromArgs(args);
|
|
@@ -327,4 +355,11 @@ function whichSync(command) {
|
|
|
327
355
|
});
|
|
328
356
|
return result.status === 0 ? String(result.stdout || '').trim().split(/\r?\n/)[0] : null;
|
|
329
357
|
}
|
|
358
|
+
function valueAfter(args = [], name) {
|
|
359
|
+
const index = args.findIndex((arg) => String(arg) === name);
|
|
360
|
+
if (index < 0)
|
|
361
|
+
return null;
|
|
362
|
+
const value = args[index + 1];
|
|
363
|
+
return value === undefined ? null : String(value);
|
|
364
|
+
}
|
|
330
365
|
//# sourceMappingURL=basic-cli.js.map
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { resolveOllamaWorkerConfig, writeLocalModelConfig, readLocalModelConfig } from '../agents/ollama-worker-config.js';
|
|
2
|
+
export async function localModelCommand(args = []) {
|
|
3
|
+
const action = normalizeLocalModelAction(args[0]);
|
|
4
|
+
if (action === 'enable')
|
|
5
|
+
return emit(await enable(args.slice(1)), args);
|
|
6
|
+
if (action === 'disable')
|
|
7
|
+
return emit(await disable(), args);
|
|
8
|
+
if (action === 'set-model')
|
|
9
|
+
return emit(await setModel(args.slice(1)), args);
|
|
10
|
+
if (action === 'status')
|
|
11
|
+
return emit(await status(), args);
|
|
12
|
+
const result = { schema: 'sks.local-model-command.v1', ok: false, action, blockers: ['unknown_local_model_action'] };
|
|
13
|
+
process.exitCode = 1;
|
|
14
|
+
return emit(result, args);
|
|
15
|
+
}
|
|
16
|
+
function normalizeLocalModelAction(value) {
|
|
17
|
+
const text = String(value || 'status').trim().toLowerCase();
|
|
18
|
+
if (['on', 'enable', 'enabled', 'with-local-llm-on', '켜', '켜기'].includes(text))
|
|
19
|
+
return 'enable';
|
|
20
|
+
if (['off', 'disable', 'disabled', 'with-local-llm-off', '꺼', '끄기'].includes(text))
|
|
21
|
+
return 'disable';
|
|
22
|
+
if (['model', 'set', 'set-model'].includes(text))
|
|
23
|
+
return 'set-model';
|
|
24
|
+
if (['', 'status', 'state', 'check'].includes(text))
|
|
25
|
+
return 'status';
|
|
26
|
+
return text;
|
|
27
|
+
}
|
|
28
|
+
async function enable(args) {
|
|
29
|
+
const model = readOption(args, '--model', args[0] || '');
|
|
30
|
+
const baseUrl = readOption(args, '--base-url', '');
|
|
31
|
+
const think = readBoolFlag(args, '--think', '--no-think');
|
|
32
|
+
const patch = { enabled: true, provider: 'ollama' };
|
|
33
|
+
if (model)
|
|
34
|
+
patch.model = model;
|
|
35
|
+
if (baseUrl)
|
|
36
|
+
patch.base_url = baseUrl;
|
|
37
|
+
if (think !== null)
|
|
38
|
+
patch.think = think;
|
|
39
|
+
const config = await writeLocalModelConfig(patch);
|
|
40
|
+
return { schema: 'sks.local-model-command.v1', ok: true, action: 'enable', config };
|
|
41
|
+
}
|
|
42
|
+
async function disable() {
|
|
43
|
+
const config = await writeLocalModelConfig({ enabled: false });
|
|
44
|
+
return { schema: 'sks.local-model-command.v1', ok: true, action: 'disable', config };
|
|
45
|
+
}
|
|
46
|
+
async function setModel(args) {
|
|
47
|
+
const model = String(args[0] || '').trim();
|
|
48
|
+
if (!model) {
|
|
49
|
+
process.exitCode = 1;
|
|
50
|
+
return { schema: 'sks.local-model-command.v1', ok: false, action: 'set-model', blockers: ['model_missing'] };
|
|
51
|
+
}
|
|
52
|
+
const config = await writeLocalModelConfig({ model });
|
|
53
|
+
return { schema: 'sks.local-model-command.v1', ok: true, action: 'set-model', config };
|
|
54
|
+
}
|
|
55
|
+
async function status() {
|
|
56
|
+
const config = await readLocalModelConfig();
|
|
57
|
+
const resolved = await resolveOllamaWorkerConfig();
|
|
58
|
+
const api = await probeOllama(resolved.base_url);
|
|
59
|
+
return { schema: 'sks.local-model-command.v1', ok: true, action: 'status', config, resolved, api };
|
|
60
|
+
}
|
|
61
|
+
async function probeOllama(baseUrl) {
|
|
62
|
+
try {
|
|
63
|
+
const response = await fetch(`${baseUrl}/api/version`, { signal: AbortSignal.timeout(3000) });
|
|
64
|
+
const text = await response.text();
|
|
65
|
+
return { ok: response.ok, status: response.status, data: response.ok ? JSON.parse(text) : null };
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function emit(result, args) {
|
|
72
|
+
if (args.includes('--json')) {
|
|
73
|
+
console.log(JSON.stringify(result, null, 2));
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
if (result.ok !== true) {
|
|
77
|
+
console.log(`Local model: blocked (${(result.blockers || []).join(', ') || 'unknown'})`);
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
const config = result.config || result.resolved || {};
|
|
81
|
+
console.log(`Local model: ${config.enabled ? 'enabled' : 'disabled'}`);
|
|
82
|
+
console.log(`Provider: ollama`);
|
|
83
|
+
console.log(`Model: ${config.model || 'unknown'}`);
|
|
84
|
+
console.log(`Base URL: ${config.base_url || config.baseUrl || 'unknown'}`);
|
|
85
|
+
if (typeof config.think === 'boolean')
|
|
86
|
+
console.log(`Think: ${config.think ? 'enabled' : 'disabled'}`);
|
|
87
|
+
if (result.api)
|
|
88
|
+
console.log(`Ollama API: ${result.api.ok ? 'ok' : 'not reachable'}`);
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
function readOption(args, name, fallback) {
|
|
92
|
+
const index = args.indexOf(name);
|
|
93
|
+
if (index >= 0 && args[index + 1] && !String(args[index + 1]).startsWith('--'))
|
|
94
|
+
return String(args[index + 1]);
|
|
95
|
+
const prefixed = args.find((arg) => String(arg).startsWith(`${name}=`));
|
|
96
|
+
return prefixed ? prefixed.slice(name.length + 1) : fallback;
|
|
97
|
+
}
|
|
98
|
+
function readBoolFlag(args, trueName, falseName) {
|
|
99
|
+
if (args.includes(trueName))
|
|
100
|
+
return true;
|
|
101
|
+
if (args.includes(falseName))
|
|
102
|
+
return false;
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=local-model-command.js.map
|
|
@@ -4,7 +4,7 @@ import { spawn } from 'node:child_process';
|
|
|
4
4
|
import { appendJsonlBounded, exists, nowIso, packageRoot, readJson, sksRoot, writeJsonAtomic } from '../fsx.js';
|
|
5
5
|
import { initProject } from '../init.js';
|
|
6
6
|
import { createMission, setCurrent } from '../mission.js';
|
|
7
|
-
import {
|
|
7
|
+
import { buildMadHighLaunchProfileNoWrite, madHighProfileName } from '../auto-review.js';
|
|
8
8
|
import { permissionGateSummary } from '../permission-gates.js';
|
|
9
9
|
import { attachZellijSessionInteractive, launchMadZellijUi, sanitizeZellijSessionName } from '../zellij/zellij-launcher.js';
|
|
10
10
|
import { createMadSksAuthorizationManifest, validateMadSksAuthorizationManifest } from '../mad-sks/authorization-manifest.js';
|
|
@@ -17,13 +17,14 @@ import { runMadSksExecutor } from '../mad-sks/executors/index.js';
|
|
|
17
17
|
import { applyMadSksRollbackPlan } from '../mad-sks/rollback-apply.js';
|
|
18
18
|
import { repairCodexConfigEperm } from '../codex/codex-config-eperm-repair.js';
|
|
19
19
|
import { runCodexLaunchPreflight } from '../preflight/parallel-preflight-engine.js';
|
|
20
|
+
import { diffCodexAppUiSnapshots, writeCodexAppUiSnapshot } from '../codex-app/codex-app-ui-state-snapshot.js';
|
|
20
21
|
export async function madHighCommand(args = [], deps = {}) {
|
|
21
22
|
const subcommand = firstSubcommand(args);
|
|
22
23
|
if (subcommand)
|
|
23
24
|
return madSksSubcommand(subcommand, args.filter((arg) => String(arg) !== subcommand));
|
|
24
25
|
const cleanArgs = stripMadLaunchOnlyArgs(args);
|
|
25
26
|
if (args.includes('--json')) {
|
|
26
|
-
const profile =
|
|
27
|
+
const profile = buildMadHighLaunchProfileNoWrite();
|
|
27
28
|
return console.log(JSON.stringify(profile, null, 2));
|
|
28
29
|
}
|
|
29
30
|
const update = deps.maybePromptSksUpdateForLaunch ? await deps.maybePromptSksUpdateForLaunch(args, { label: 'MAD launch' }) : { status: 'skipped' };
|
|
@@ -55,16 +56,28 @@ export async function madHighCommand(args = [], deps = {}) {
|
|
|
55
56
|
process.exitCode = 1;
|
|
56
57
|
return;
|
|
57
58
|
}
|
|
58
|
-
const profile =
|
|
59
|
+
const profile = buildMadHighLaunchProfileNoWrite();
|
|
59
60
|
const launchRoot = process.cwd();
|
|
60
61
|
if (!(await exists(path.join(launchRoot, '.sneakoscope'))))
|
|
61
62
|
await initProject(launchRoot, {});
|
|
63
|
+
const uiSnapshotId = Date.now().toString(36);
|
|
64
|
+
const beforeUi = await writeCodexAppUiSnapshot(launchRoot, `mad-before-${uiSnapshotId}`).catch(() => null);
|
|
62
65
|
// launchFast skips the redundant live-`codex exec` config probe (up to ~20s, run
|
|
63
66
|
// up to 3x via repair re-inspections): the real codex profile is exercised moments
|
|
64
67
|
// later when the Zellij session opens. All filesystem/permission/EPERM/symlink/ACL
|
|
65
68
|
// readability + repair checks still run. SKS_LAUNCH_FULL_CODEX_PROBE=1 restores the
|
|
66
69
|
// old behavior.
|
|
67
|
-
const
|
|
70
|
+
const rawArgs = (args || []).map((arg) => String(arg));
|
|
71
|
+
const allowMadRepair = rawArgs.includes('--repair-config') || rawArgs.includes('--fix') || rawArgs.includes('--yes-repair');
|
|
72
|
+
const launchPreflight = await runCodexLaunchPreflight(launchRoot, { fix: allowMadRepair, launchFast: process.env.SKS_LAUNCH_FULL_CODEX_PROBE !== '1', profile: profile.profile_name, sandbox: 'danger-full-access', serviceTier: 'fast' });
|
|
73
|
+
const afterPreflightUi = beforeUi ? await writeCodexAppUiSnapshot(launchRoot, `mad-after-preflight-${uiSnapshotId}`).catch(() => null) : null;
|
|
74
|
+
const preflightUiDiff = beforeUi && afterPreflightUi ? diffCodexAppUiSnapshots(beforeUi, afterPreflightUi) : null;
|
|
75
|
+
if (preflightUiDiff && !preflightUiDiff.ok) {
|
|
76
|
+
await writeJsonAtomic(path.join(launchRoot, '.sneakoscope', 'reports', 'mad-codex-app-ui-preflight-diff.json'), preflightUiDiff);
|
|
77
|
+
console.error('SKS MAD launch changed Codex App UI state during preflight. Run `sks doctor --fix`.');
|
|
78
|
+
process.exitCode = 1;
|
|
79
|
+
return preflightUiDiff;
|
|
80
|
+
}
|
|
68
81
|
if (!launchPreflight.ok) {
|
|
69
82
|
console.error('SKS MAD launch blocked by config preflight.');
|
|
70
83
|
for (const blocker of launchPreflight.blockers || [])
|
|
@@ -83,16 +96,30 @@ export async function madHighCommand(args = [], deps = {}) {
|
|
|
83
96
|
SKS_MAD_SKS_TARGET_ROOT: madLaunch.gate.cwd,
|
|
84
97
|
SKS_MAD_SKS_PROTECTED_CORE_DIGEST: madLaunch.gate.protected_core_digest
|
|
85
98
|
};
|
|
86
|
-
const madNativeSwarm = await startMadNativeSwarm(madLaunch.root, madLaunch, args, profile, {
|
|
87
|
-
env: madSksEnv
|
|
88
|
-
});
|
|
89
99
|
const launchOpts = codexLbImmediateLaunchOpts(cleanArgs, launchLb, { codexArgs: profile.launch_args, conciseBlockers: true, madSksEnv, launchEnv: madSksEnv });
|
|
90
100
|
const workspace = readOption(cleanArgs, '--workspace', readOption(cleanArgs, '--session', launchOpts.session || `sks-mad-${sanitizeZellijSessionName(process.cwd())}`));
|
|
91
|
-
const launch = await launchMadZellijUi([...cleanArgs, '--workspace', workspace], { ...launchOpts, missionId: madLaunch.mission_id, root: madLaunch.root, cwd: process.cwd(), ledgerRoot: path.join(madLaunch.dir, 'agents'), slotCount:
|
|
101
|
+
const launch = await launchMadZellijUi([...cleanArgs, '--workspace', workspace], { ...launchOpts, missionId: madLaunch.mission_id, root: madLaunch.root, cwd: process.cwd(), ledgerRoot: path.join(madLaunch.dir, 'agents'), slotCount: 0, requireZellij: process.env.SKS_REQUIRE_ZELLIJ === '1' });
|
|
102
|
+
const afterLaunchUi = beforeUi ? await writeCodexAppUiSnapshot(launchRoot, `mad-after-launch-${uiSnapshotId}`).catch(() => null) : null;
|
|
103
|
+
const launchUiDiff = beforeUi && afterLaunchUi ? diffCodexAppUiSnapshots(beforeUi, afterLaunchUi) : null;
|
|
104
|
+
if (launchUiDiff) {
|
|
105
|
+
await writeJsonAtomic(path.join(madLaunch.dir, 'codex-app-ui-diff.json'), launchUiDiff);
|
|
106
|
+
if (!launchUiDiff.ok) {
|
|
107
|
+
console.error('SKS MAD launch changed Codex App UI state. Run `sks doctor --fix`.');
|
|
108
|
+
process.exitCode = 1;
|
|
109
|
+
return launchUiDiff;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
92
112
|
if (!launch.ok) {
|
|
93
113
|
console.log(`MAD Zellij action: ${formatMadZellijAction(launch)}`);
|
|
94
114
|
return launch;
|
|
95
115
|
}
|
|
116
|
+
const madNativeSwarm = await startMadNativeSwarm(madLaunch.root, madLaunch, args, profile, {
|
|
117
|
+
env: {
|
|
118
|
+
...madSksEnv,
|
|
119
|
+
SKS_ZELLIJ_SESSION_NAME: launch.session_name
|
|
120
|
+
},
|
|
121
|
+
zellijSessionName: launch.session_name
|
|
122
|
+
});
|
|
96
123
|
// The launcher only creates a detached background session. In an interactive
|
|
97
124
|
// terminal, immediately attach so the session actually opens for the user
|
|
98
125
|
// instead of leaving them to copy/paste the attach command by hand.
|
|
@@ -162,6 +189,11 @@ export async function startMadNativeSwarm(root, madLaunch, args = [], profile =
|
|
|
162
189
|
'--fast',
|
|
163
190
|
'--json'
|
|
164
191
|
];
|
|
192
|
+
if (swarm.backend === 'zellij') {
|
|
193
|
+
command.push('--real');
|
|
194
|
+
command.push('--zellij-session-name', opts.zellijSessionName || `sks-${madLaunch.mission_id}`);
|
|
195
|
+
command.push('--zellij-pane-worker');
|
|
196
|
+
}
|
|
165
197
|
const baseReport = {
|
|
166
198
|
schema: 'sks.mad-sks-native-swarm.v1',
|
|
167
199
|
ok: true,
|
|
@@ -176,6 +208,7 @@ export async function startMadNativeSwarm(root, madLaunch, args = [], profile =
|
|
|
176
208
|
target_active_slots: swarm.agents,
|
|
177
209
|
work_items: swarm.workItems,
|
|
178
210
|
backend: swarm.backend,
|
|
211
|
+
zellij_session_name: opts.zellijSessionName || null,
|
|
179
212
|
readonly: true,
|
|
180
213
|
command,
|
|
181
214
|
stdout_log: path.relative(root, stdoutLog),
|
|
@@ -228,7 +261,7 @@ export function resolveMadNativeSwarmOptions(args = [], profile = {}, opts = {})
|
|
|
228
261
|
const disabled = list.includes('--no-swarm') || list.includes('--no-mad-swarm') || process.env.SKS_MAD_NATIVE_SWARM === '0';
|
|
229
262
|
const agents = clampInt(readOption(list, '--mad-agents', readOption(list, '--mad-swarm-agents', process.env.SKS_MAD_SWARM_AGENTS || opts.agents || 5)), 1, 20);
|
|
230
263
|
const workItems = clampInt(readOption(list, '--mad-swarm-work-items', process.env.SKS_MAD_SWARM_WORK_ITEMS || opts.workItems || agents), agents, 100);
|
|
231
|
-
const backend =
|
|
264
|
+
const backend = defaultMadSwarmBackend(list, opts);
|
|
232
265
|
return {
|
|
233
266
|
enabled: !disabled,
|
|
234
267
|
disabled_reason: disabled ? 'operator_disabled_mad_native_swarm' : null,
|
|
@@ -405,6 +438,9 @@ function madLaunchOnlyFlags() {
|
|
|
405
438
|
'--mad-swarm-work-items',
|
|
406
439
|
'--mad-swarm-backend',
|
|
407
440
|
'--mad-swarm-prompt',
|
|
441
|
+
'--repair-config',
|
|
442
|
+
'--fix',
|
|
443
|
+
'--yes-repair',
|
|
408
444
|
'--yes',
|
|
409
445
|
'-y',
|
|
410
446
|
'--dry-run',
|
|
@@ -420,6 +456,19 @@ function madLaunchValueFlags() {
|
|
|
420
456
|
'--mad-swarm-prompt'
|
|
421
457
|
]);
|
|
422
458
|
}
|
|
459
|
+
export function defaultMadSwarmBackend(args = [], opts = {}) {
|
|
460
|
+
const list = (args || []).map((arg) => String(arg));
|
|
461
|
+
const explicit = readOption(list, '--mad-swarm-backend', null);
|
|
462
|
+
if (explicit)
|
|
463
|
+
return String(explicit);
|
|
464
|
+
if (process.env.SKS_MAD_SWARM_BACKEND)
|
|
465
|
+
return String(process.env.SKS_MAD_SWARM_BACKEND);
|
|
466
|
+
if (opts.backend)
|
|
467
|
+
return String(opts.backend);
|
|
468
|
+
if (list.includes('--json') || list.includes('--no-attach') || opts.nonInteractive === true)
|
|
469
|
+
return 'codex-sdk';
|
|
470
|
+
return 'zellij';
|
|
471
|
+
}
|
|
423
472
|
function stripMadLaunchOnlyArgs(args = []) {
|
|
424
473
|
const flags = madLaunchOnlyFlags();
|
|
425
474
|
const valueFlags = madLaunchValueFlags();
|
|
@@ -231,7 +231,7 @@ async function executeRouteCommand(root, route, prompt, { auto = false } = {}) {
|
|
|
231
231
|
return routeExecutionResult(route, ['sks', ...commandArgs].join(' '), result, {
|
|
232
232
|
okStatus: 'completed',
|
|
233
233
|
trustStatus: 'verified_partial',
|
|
234
|
-
executionKind: route.command === '$DB' || route.command === '$Wiki' || route.command === '$Fast-Mode' ? 'safe_deterministic' : 'mock_safe',
|
|
234
|
+
executionKind: route.command === '$DB' || route.command === '$Wiki' || route.command === '$Fast-Mode' || route.command === '$with-local-llm-on' ? 'safe_deterministic' : 'mock_safe',
|
|
235
235
|
});
|
|
236
236
|
}
|
|
237
237
|
async function runAutoVerification(root, missionId) {
|
|
@@ -346,6 +346,8 @@ function safeRouteExecutionArgs(route, prompt, { auto = false } = {}) {
|
|
|
346
346
|
return ['wiki', 'refresh', '--json'];
|
|
347
347
|
if (route.command === '$Fast-Mode')
|
|
348
348
|
return ['fast-mode', fastModeActionFromPrompt(prompt), '--json'];
|
|
349
|
+
if (route.command === '$with-local-llm-on')
|
|
350
|
+
return ['with-local-llm', localModelActionFromPrompt(prompt), '--json'];
|
|
349
351
|
return ['team', prompt, '--mock', '--json', ...(auto ? ['--no-open-zellij'] : [])];
|
|
350
352
|
}
|
|
351
353
|
function fastModeActionFromPrompt(prompt = '') {
|
|
@@ -372,6 +374,32 @@ function fastModeActionFromPrompt(prompt = '') {
|
|
|
372
374
|
return 'clear';
|
|
373
375
|
return 'status';
|
|
374
376
|
}
|
|
377
|
+
function localModelActionFromPrompt(prompt = '') {
|
|
378
|
+
const text = String(prompt || '');
|
|
379
|
+
const lower = text.toLowerCase();
|
|
380
|
+
if (/\$with-local-llm-off\b/.test(lower))
|
|
381
|
+
return 'disable';
|
|
382
|
+
if (/\$with-local-llm-on\b/.test(lower))
|
|
383
|
+
return 'enable';
|
|
384
|
+
const routeMatch = /\$with-local-llm\b/i.exec(text);
|
|
385
|
+
if (!routeMatch)
|
|
386
|
+
return 'status';
|
|
387
|
+
const afterRoute = text
|
|
388
|
+
.slice(routeMatch.index + routeMatch[0].length)
|
|
389
|
+
.replace(/^[\s:=\-]+/, '')
|
|
390
|
+
.trimStart()
|
|
391
|
+
.toLowerCase();
|
|
392
|
+
const token = afterRoute.match(/^[^\s?!.,;:()"'`]+/)?.[0] || '';
|
|
393
|
+
if (['off', 'disable', 'disabled', '끄기', '꺼', '꺼줘'].includes(token) || token.startsWith('끄') || token.startsWith('꺼'))
|
|
394
|
+
return 'disable';
|
|
395
|
+
if (['on', 'enable', 'enabled', '켜기', '켜', '켜줘'].includes(token) || token.startsWith('켜'))
|
|
396
|
+
return 'enable';
|
|
397
|
+
if (['model', 'set-model', 'set'].includes(token))
|
|
398
|
+
return 'set-model';
|
|
399
|
+
if (['status', 'state', 'check', '확인', '상태'].includes(token))
|
|
400
|
+
return 'status';
|
|
401
|
+
return 'status';
|
|
402
|
+
}
|
|
375
403
|
function destructiveDbPrompt(prompt = '') {
|
|
376
404
|
return /\b(drop|truncate|delete\s+from|update\s+\w+\s+set|reset|db\s+push|disable\s+rls)\b/i.test(prompt);
|
|
377
405
|
}
|