@hecer/yoke 1.7.0 → 1.8.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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +23 -0
- package/README.md +23 -18
- package/dist/agents/providers.js +10 -3
- package/dist/cli.js +5 -5
- package/dist/dashboard/analytics.js +123 -0
- package/dist/dashboard/page.js +6 -4
- package/dist/dashboard/panels.js +19 -0
- package/dist/dashboard/server.js +20 -3
- package/dist/goals/command.js +5 -1
- package/dist/loop/dispatcher.js +5 -2
- package/dist/loop/git.js +1 -1
- package/dist/loop/parallel-command.js +43 -4
- package/dist/loop/reporter.js +25 -9
- package/dist/loop/run-command.js +44 -12
- package/dist/loop/runner.js +12 -5
- package/dist/observability/events.js +6 -1
- package/dist/observability/history.js +80 -0
- package/dist/quality/candidate-comparison.js +1 -1
- package/dist/quality/command.js +16 -3
- package/dist/retrofit/config.js +2 -0
- package/dist/retrofit/gitignore.js +1 -0
- package/dist/routing/router.js +31 -9
- package/dist/setup/command.js +3 -2
- package/docs/PRODUCT-DIRECTION-2026-09-05.md +16 -0
- package/docs/VERIFIED-PROJECTS.md +20 -0
- package/gemini-extension.json +1 -1
- package/hooks/bounded-gemini.mjs +70 -0
- package/package.json +1 -1
package/dist/routing/router.js
CHANGED
|
@@ -112,6 +112,26 @@ function callUsage(role, provider, selection, tokens, durationMs, profile) {
|
|
|
112
112
|
};
|
|
113
113
|
}
|
|
114
114
|
export function makeAdaptiveRunner(options) {
|
|
115
|
+
const run = routingSteps(options);
|
|
116
|
+
return ctx => {
|
|
117
|
+
const steps = run(ctx);
|
|
118
|
+
let next = steps.next();
|
|
119
|
+
while (!next.done)
|
|
120
|
+
next = steps.next(next.value());
|
|
121
|
+
return next.value;
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
export function makeAsyncAdaptiveRunner(options) {
|
|
125
|
+
const run = routingSteps(options);
|
|
126
|
+
return async (ctx) => {
|
|
127
|
+
const steps = run(ctx);
|
|
128
|
+
let next = steps.next();
|
|
129
|
+
while (!next.done)
|
|
130
|
+
next = steps.next(await next.value());
|
|
131
|
+
return next.value;
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function routingSteps(options) {
|
|
115
135
|
const now = options.now ?? Date.now;
|
|
116
136
|
const available = options.isAvailable ?? (() => true);
|
|
117
137
|
const eligibleWorkers = options.workers.filter(worker => available(worker.agent));
|
|
@@ -121,12 +141,12 @@ export function makeAdaptiveRunner(options) {
|
|
|
121
141
|
permissions: options.permissions ?? 'safe',
|
|
122
142
|
selection,
|
|
123
143
|
}));
|
|
124
|
-
return (ctx)
|
|
144
|
+
return function* (ctx) {
|
|
125
145
|
// Re-rank per story so a long-running loop can use gate outcomes learned by
|
|
126
146
|
// earlier stories without rebuilding the runner.
|
|
127
147
|
const rule = options.rules?.find(rule => (!rule.area || rule.area === ctx.story.area) && (!rule.storyId || rule.storyId === ctx.story.id) && (rule.area || rule.storyId));
|
|
128
148
|
if (rule) {
|
|
129
|
-
const project = projectHash(ctx.targetDir);
|
|
149
|
+
const project = projectHash(options.projectRoot ?? ctx.targetDir);
|
|
130
150
|
const prior = readRoutingObservations().reverse().find(event => event.projectHash === project && event.storyHash === storyHash(project, ctx.story.id) && typeof event.verificationSuccess === 'boolean');
|
|
131
151
|
if (prior?.verificationSuccess === false)
|
|
132
152
|
failedStories.add(ctx.story.id);
|
|
@@ -134,12 +154,12 @@ export function makeAdaptiveRunner(options) {
|
|
|
134
154
|
const ruleWorker = rule && failedStories.has(ctx.story.id) ? rule.escalateTo ?? 'SELF' : rule?.worker;
|
|
135
155
|
const candidates = rule ? eligibleWorkers : rankWorkers(eligibleWorkers, options.strategy, options.maxCandidates);
|
|
136
156
|
if (candidates.length === 0) {
|
|
137
|
-
return makeWorker(options.parent, options.parentSelection ?? {})(ctx);
|
|
157
|
+
return yield () => makeWorker(options.parent, options.parentSelection ?? {})(ctx);
|
|
138
158
|
}
|
|
139
159
|
const prompt = buildRoutingPrompt(ctx, candidates, options.strategy);
|
|
140
|
-
const orchestratorSelection = { ...(options.parentSelection ?? {}), ...(options.orchestratorSelection ?? {}),
|
|
160
|
+
const orchestratorSelection = { ...(options.parentSelection ?? {}), ...(options.orchestratorSelection ?? {}), nativeMultiAgent: false };
|
|
141
161
|
const orchestratorStarted = now();
|
|
142
|
-
const routeRun = rule ? { success: true, summary: 'Explicit rule', output: '', tokens: { inputTokens: 0, outputTokens: 0 } } : options.captureRoute
|
|
162
|
+
const routeRun = rule ? { success: true, summary: 'Explicit rule', output: '', tokens: { inputTokens: 0, outputTokens: 0 } } : yield () => options.captureRoute
|
|
143
163
|
? options.captureRoute(options.parent, ctx, prompt, orchestratorSelection)
|
|
144
164
|
: runCapturedAgent(options.parent, buildWatchdogInvocation(runnerInvocation(options.parent, prompt, ctx.targetDir, true, 'read-only', orchestratorSelection), options.idleTimeoutMs ?? 0));
|
|
145
165
|
const orchestratorDurationMs = Math.max(0, now() - orchestratorStarted);
|
|
@@ -150,16 +170,18 @@ export function makeAdaptiveRunner(options) {
|
|
|
150
170
|
const worker = selected === 'SELF' ? undefined : candidates.find(candidate => candidate.id === selected);
|
|
151
171
|
const provider = worker?.agent ?? options.parent;
|
|
152
172
|
const selection = worker
|
|
153
|
-
? { model: worker.model, reasoningEffort: worker.reasoningEffort,
|
|
154
|
-
: { ...(options.parentSelection ?? {}),
|
|
173
|
+
? { model: worker.model, reasoningEffort: worker.reasoningEffort, nativeMultiAgent: false, ...(provider !== 'gemini' ? { bare: options.parentSelection?.bare } : {}) }
|
|
174
|
+
: { ...(options.parentSelection ?? {}), nativeMultiAgent: false };
|
|
155
175
|
const workerStarted = now();
|
|
156
|
-
const result = makeWorker(provider, selection)(ctx);
|
|
176
|
+
const result = yield () => makeWorker(provider, selection)(ctx);
|
|
157
177
|
const workerDurationMs = Math.max(0, now() - workerStarted);
|
|
158
178
|
const calls = [
|
|
159
179
|
...(!rule ? [callUsage('orchestrator', options.parent, orchestratorSelection, routeRun.tokens, orchestratorDurationMs)] : []),
|
|
160
180
|
callUsage(worker ? 'worker' : 'parent', provider, selection, result.tokens, workerDurationMs, selected),
|
|
161
181
|
];
|
|
162
182
|
const tokens = {
|
|
183
|
+
storyId: ctx.story.id,
|
|
184
|
+
escalated: Boolean(rule && failedStories.has(ctx.story.id)),
|
|
163
185
|
inputTokens: (routeRun.tokens?.inputTokens ?? 0) + (result.tokens?.inputTokens ?? 0),
|
|
164
186
|
...((routeRun.tokens?.cachedInputTokens !== undefined || result.tokens?.cachedInputTokens !== undefined)
|
|
165
187
|
? { cachedInputTokens: (routeRun.tokens?.cachedInputTokens ?? 0) + (result.tokens?.cachedInputTokens ?? 0) }
|
|
@@ -188,7 +210,7 @@ export function makeAdaptiveRunner(options) {
|
|
|
188
210
|
failedStories.add(ctx.story.id);
|
|
189
211
|
else
|
|
190
212
|
failedStories.delete(ctx.story.id);
|
|
191
|
-
const project = projectHash(ctx.targetDir);
|
|
213
|
+
const project = projectHash(options.projectRoot ?? ctx.targetDir);
|
|
192
214
|
recordRoutingObservation({
|
|
193
215
|
projectHash: project,
|
|
194
216
|
storyHash: storyHash(project, ctx.story.id),
|
package/dist/setup/command.js
CHANGED
|
@@ -46,7 +46,7 @@ export async function runSetup(targetDir, opts = {}) {
|
|
|
46
46
|
const defaultLoop = opts.loop ?? existing?.loop.enabled ?? true;
|
|
47
47
|
const defaultRunner = opts.runner ?? existing?.runner?.agent ?? (host && defaultAgents.includes(host) ? host : defaultAgents[0] ?? host ?? 'claude');
|
|
48
48
|
const defaultPolicy = opts.decisionPolicy ?? existing?.loop.decisionPolicy ?? (existing?.loop.onAmbiguity === 'abort' ? 'critical' : 'auto');
|
|
49
|
-
const defaultRouting = opts.routing ?? existing?.routing?.enabled ??
|
|
49
|
+
const defaultRouting = opts.routing ?? existing?.routing?.enabled ?? true;
|
|
50
50
|
const interactive = opts.interactive ?? (process.stdin.isTTY === true && process.stdout.isTTY === true);
|
|
51
51
|
let close;
|
|
52
52
|
let ask = opts.ask;
|
|
@@ -84,10 +84,11 @@ export async function runSetup(targetDir, opts = {}) {
|
|
|
84
84
|
const config = loadConfig(targetDir);
|
|
85
85
|
if (!config)
|
|
86
86
|
return 1;
|
|
87
|
-
config.loop = { ...config.loop, enabled: loop, decisionPolicy };
|
|
87
|
+
config.loop = { parallel: 'auto', isolate: true, ...config.loop, enabled: loop, decisionPolicy };
|
|
88
88
|
config.runner = { ...config.runner, agent: runner };
|
|
89
89
|
const existingWorkers = config.routing?.workers ?? [];
|
|
90
90
|
config.routing = {
|
|
91
|
+
...config.routing,
|
|
91
92
|
enabled: routing,
|
|
92
93
|
strategy: config.routing?.strategy ?? 'balanced',
|
|
93
94
|
maxCandidates: config.routing?.maxCandidates ?? 3,
|
|
@@ -181,3 +181,19 @@ Am 2026-09-05 gelesene Primärquellen; vor konkreten Versions-/Preisentscheidung
|
|
|
181
181
|
- https://github.com/open-gsd/gsd-core
|
|
182
182
|
|
|
183
183
|
Provenienz der ursprünglichen README-Prüfung: kein C2PA gefunden, unterstützter Scan vollständig, Verifikation/Vertrauen/Metadatenprivatsphäre unbekannt. Unicode-Befund: Emoji-Variationszeichen, kein Nachweis eines KI-Wasserzeichens. Proprietäre Wasserzeichen nicht überprüfbar. Dieses Gesprächsdokument wurde vom KI-Assistenten aus der Sitzung zusammengefasst; es enthält keine unabhängige Bestätigung der Produktthesen.
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
## Fortsetzung am 2026-09-06: Defaults und Dashboard
|
|
187
|
+
|
|
188
|
+
Der Nutzer hat die kombinierte Umsetzung von automatischem Routing/Parallelismus und den drei Dashboardansichten Jetzt, Verbrauch & Zeit sowie Ergebnisse beauftragt. Die Änderungen liegen lokal als unveröffentlichter Ausbau vor; Paketversion und zuletzt veröffentlichtes Release bleiben 1.7.0.
|
|
189
|
+
|
|
190
|
+
Umgesetzt: Routing im asynchronen Workerpfad; neue Setups mit Routing an, Parallelität auto und Isolation an; konservativ höchstens zwei Yoke-Worker bei deklarierten Schreibbereichen; Respektierung expliziter Einstellungen; dauerhafte Messhistorie getrennt von der kurzen Aktivitätsliste; Tages-/Wochen-/Monatsauswertung in UTC; Modell- und Projektvergleich; Verbrauchsdiagramm; aktuelle Aufgaben und Phasen; Abnahmen und Aufwand pro Abnahme. Verfügbare Reviewer-, Kritiker- und Reparaturnutzung wird mit erfasst. Details und Grenzen stehen in VERIFIED-PROJECTS.md.
|
|
191
|
+
|
|
192
|
+
Weiterhin keine behaupteten Fähigkeiten: dynamische gemeinsame Nutzung von Slots durch native Subagenten (native Delegation ist für Loop-Aufrufe bei allen drei Anbietern deaktiviert), exakte Generierungsgeschwindigkeit, vollständige Rekonstruktion alter Verbrauchsdaten, automatische monatliche Archivverdichtung oder kalibrierte Zeitprognosen. Bestehende Quality-Reparaturlimits bleiben erhalten; konkurrierende Kandidaten bleiben optional.
|
|
193
|
+
|
|
194
|
+
Die Umsetzung wurde mit Tests und einer lokalen Browserprüfung geprüft; authentifizierte Modellbenchmarks und Veröffentlichung waren kein Bestandteil dieser Fortsetzung. Dieses Update wurde vom KI-Assistenten aus der laufenden Umsetzung festgehalten.
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
### Releaseauftrag am 2026-09-06
|
|
198
|
+
|
|
199
|
+
Der Nutzer hat anschließend maximal drei Worker im Automatikmodus und die Veröffentlichung der Weiterentwicklung beauftragt. Releaseziel ist 1.8.0; der frühere lokale Zwischenstand mit zwei Workern ist damit überholt. Jede neue Version muss vor Veröffentlichung einen datierten Changelogeintrag erhalten; die verbindliche Regel steht in AGENTS.md. Der tatsächliche Veröffentlichungsstatus wird über GitHub Release und npm geprüft.
|
|
@@ -130,6 +130,26 @@ Versioned local events record status, phase duration, attempts and available usa
|
|
|
130
130
|
|
|
131
131
|
## Local project dashboard
|
|
132
132
|
|
|
133
|
+
### Execution defaults in 1.8.0
|
|
134
|
+
|
|
135
|
+
New setups enable routing, `loop.parallel: auto` and `loop.isolate: true`. Existing explicit settings remain authoritative. At execution time, automatic routing uses configured profiles; without profiles it keeps the selected parent. Explicit `--routing` without profiles still reports a configuration error. Routing rules bypass the controller and can escalate following failed independent gates, including across worktrees and restarts. Routing now also runs asynchronously inside parallel workers. An explicit task provider affinity takes precedence over routing.
|
|
136
|
+
|
|
137
|
+
Automatic parallelism allows at most three Yoke workers when every pending task declares nonempty write scopes. Dependencies and overlapping scopes still constrain dispatch. Unknown scopes, configured tool actions and worktree recovery select serial execution. Use `--parallel=N`, `--parallel=auto`, `--no-routing` or `--no-isolate` to override defaults. Serial worktrees retain failed work and require deliberate recovery; the default does not discard an existing recovery tree. Quality repair budgets and opt-in competing candidates retain their existing policies.
|
|
138
|
+
|
|
139
|
+
Integration retains an execution slot until its candidate lands. Yoke loop runners disable native delegation so it cannot multiply the default worker budget: Codex disables multi_agent; Claude disallows Agent, Task, TeamCreate and SendMessage; Gemini uses a separate temporary system-settings copy that disables experimental agents and its always-on investigator/help overrides. Existing Gemini system policy and system-default paths are preserved; unreadable or malformed policy blocks launch. Original settings are never overwritten. The temporary copy is removed on normal exit; forced process termination may leave a private temporary directory. Explicit competing candidate counts remain a separate opt-in workload.
|
|
140
|
+
|
|
141
|
+
### Dashboard views
|
|
142
|
+
|
|
143
|
+
- **Now:** reported task and worker activity, requested provider/model, elapsed worker time, phase, integration, blockers, status age, backlog and empirical remaining-time ranges. The view refreshes every five seconds while visible and not being operated with keyboard focus. A stale status is marked rather than asserted to be live.
|
|
144
|
+
- **Usage & time:** last 24 hours, 7/30/90/365 days or custom dates, grouped by UTC day, Monday-based week or month. Displays reported input/output and cache categories, cost coverage, consumption charts, per-model time buckets, per-task usage and summed phase/call durations. The overview also compares projects for the same period.
|
|
145
|
+
- **Results:** recorded acceptances, ended attempts, explicitly successful outcomes, repair phases, rule-driven escalations and recorded tokens/time per acceptance. Saved acceptance evidence and goal attempts remain available; their timestamps may fall outside the statistics period.
|
|
146
|
+
|
|
147
|
+
The short activity list still retains at most 1,000 events. Compact measurements now also persist under `.yoke/history/YYYY-MM-DD/` independently of that retention and across runs. They contain identifiers, model/provider evidence and measurements, not prompts or full status snapshots. Runtime history is excluded from Yoke commits and added to new project ignore rules. Available reviewer, quality critic and repair usage is recorded separately; absent usage is counted as unknown. Recent and archived records are deduplicated by event ID.
|
|
148
|
+
|
|
149
|
+
Dates and buckets use UTC. The custom end date is inclusive; the API uses an exclusive upper timestamp. Usage belongs to the time the provider reports it, and durations to their end time. Tokens per elapsed minute divide recorded input/output by the full selected interval. Tokens per call minute use summed reported call duration, which can overlap across workers. Neither is measured generation speed. Cache categories are shown separately without adding them to input again.
|
|
150
|
+
|
|
151
|
+
Historical activity that was never recorded or already expired cannot be reconstructed. Queue/human waiting time that lacks measurements remains unknown; phase and attempt durations are summed worker time, not automatically elapsed project time. Queries allow at most 366 days and bounded reads (8 MiB per shard, 32 MiB overall, 50,000 records); skipped, malformed or oversized history is reported as incomplete. History currently requires local disk retention rather than automatic monthly compaction. Unknown model identity is never replaced by a requested model, and missing charges are not estimated from token counts.
|
|
152
|
+
|
|
133
153
|
```sh
|
|
134
154
|
yoke projects add /path/to/project
|
|
135
155
|
yoke projects list
|
package/gemini-extension.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yoke",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Cross-agent coding harness: curated skill canon, mechanical safety gates, autonomous loop with proof artifacts. CLI: npm i -g @hecer/yoke",
|
|
5
5
|
"contextFileName": "GEMINI-EXTENSION.md"
|
|
6
6
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process'
|
|
2
|
+
import { readFileSync, statSync, mkdtempSync, writeFileSync, unlinkSync, rmdirSync } from 'node:fs'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import { dirname, join } from 'node:path'
|
|
5
|
+
import { pathToFileURL } from 'node:url'
|
|
6
|
+
|
|
7
|
+
// Match Gemini's JSON-with-comments reader without interpreting executable content.
|
|
8
|
+
export function parseSettings(source) {
|
|
9
|
+
let clean = '', quoted = false, escaped = false, line = false, block = false
|
|
10
|
+
for (let i = 0; i < source.length; i++) {
|
|
11
|
+
const c = source[i], next = source[i + 1]
|
|
12
|
+
if (line) { if (c === '\n' || c === '\r') { line = false; clean += c } else clean += ' '; continue }
|
|
13
|
+
if (block) { if (c === '*' && next === '/') { block = false; clean += ' '; i++ } else clean += c === '\n' ? '\n' : ' '; continue }
|
|
14
|
+
if (quoted) { clean += c; if (escaped) escaped = false; else if (c === '\\') escaped = true; else if (c === '"') quoted = false; continue }
|
|
15
|
+
if (c === '"') quoted = true
|
|
16
|
+
else if (c === '/' && next === '/') { line = true; clean += ' '; i++; continue }
|
|
17
|
+
else if (c === '/' && next === '*') { block = true; clean += ' '; i++; continue }
|
|
18
|
+
clean += c
|
|
19
|
+
}
|
|
20
|
+
if (block) throw Error('Invalid settings comment')
|
|
21
|
+
const value = JSON.parse(clean)
|
|
22
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw Error('Settings must be an object')
|
|
23
|
+
return value
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function prepareGeminiEnvironment(environment = process.env, platform = process.platform) {
|
|
27
|
+
const original = environment.GEMINI_CLI_SYSTEM_SETTINGS_PATH ?? (platform === 'win32' ? 'C:\\ProgramData\\gemini-cli\\settings.json' : platform === 'darwin' ? '/Library/Application Support/GeminiCli/settings.json' : '/etc/gemini-cli/settings.json')
|
|
28
|
+
let settings = {}
|
|
29
|
+
try {
|
|
30
|
+
if (statSync(original).size > 1048576) throw Error('Settings too large')
|
|
31
|
+
settings = parseSettings(readFileSync(original, 'utf8'))
|
|
32
|
+
} catch (error) {
|
|
33
|
+
if (error.code !== 'ENOENT') throw Error('Cannot safely preserve existing Gemini system settings')
|
|
34
|
+
}
|
|
35
|
+
if (settings.experimental !== undefined && (!settings.experimental || typeof settings.experimental !== 'object' || Array.isArray(settings.experimental))) throw Error('Invalid Gemini experimental settings')
|
|
36
|
+
if (settings.agents !== undefined && (!settings.agents || typeof settings.agents !== 'object' || Array.isArray(settings.agents))) throw Error('Invalid Gemini agent settings')
|
|
37
|
+
if (settings.agents?.overrides !== undefined && (!settings.agents.overrides || typeof settings.agents.overrides !== 'object' || Array.isArray(settings.agents.overrides))) throw Error('Invalid Gemini agent overrides')
|
|
38
|
+
const dir = mkdtempSync(join(tmpdir(), 'yoke-gemini-'))
|
|
39
|
+
const file = join(dir, 'settings.json')
|
|
40
|
+
const cleanup = () => { try { unlinkSync(file) } catch {} try { rmdirSync(dir) } catch {} }
|
|
41
|
+
const overrides = { ...settings.agents?.overrides }
|
|
42
|
+
// Gemini exposes these built-ins even when experimental agents are disabled.
|
|
43
|
+
for (const name of ['codebase_investigator', 'cli_help']) overrides[name] = { ...overrides[name], enabled: false }
|
|
44
|
+
try { writeFileSync(file, JSON.stringify({ ...settings, experimental: { ...settings.experimental, enableAgents: false }, agents: { ...settings.agents, overrides } }), { mode: 0o600, flag: 'wx' }) }
|
|
45
|
+
catch (error) { cleanup(); throw error }
|
|
46
|
+
return {
|
|
47
|
+
env: { ...environment, GEMINI_CLI_SYSTEM_SETTINGS_PATH: file,
|
|
48
|
+
GEMINI_CLI_SYSTEM_DEFAULTS_PATH: environment.GEMINI_CLI_SYSTEM_DEFAULTS_PATH ?? join(dirname(original), 'system-defaults.json') },
|
|
49
|
+
cleanup,
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (process.argv[1] && pathToFileURL(process.argv[1]).href === import.meta.url) {
|
|
54
|
+
let prepared
|
|
55
|
+
try {
|
|
56
|
+
prepared = prepareGeminiEnvironment()
|
|
57
|
+
// Keep Windows shim arguments shell-inert. Prompts use inherited stdin only.
|
|
58
|
+
const args = process.argv.slice(2)
|
|
59
|
+
const windows = process.platform === 'win32'
|
|
60
|
+
if (windows && args.some(arg => !/^[A-Za-z0-9_./:=+-]+$/u.test(arg))) throw Error('Unsafe Gemini argument')
|
|
61
|
+
const child = spawn(windows ? 'cmd.exe' : 'gemini', windows ? ['/d', '/s', '/c', ['gemini', ...args].join(' ')] : args, { stdio: 'inherit', shell: false, env: prepared.env })
|
|
62
|
+
process.once('exit', prepared.cleanup)
|
|
63
|
+
child.once('error', () => { process.stderr.write('Could not start the Gemini CLI\n'); process.exitCode = 127; prepared.cleanup() })
|
|
64
|
+
child.once('close', code => { process.exitCode ??= code === null || code < 0 ? 1 : code; prepared.cleanup() })
|
|
65
|
+
} catch {
|
|
66
|
+
prepared?.cleanup()
|
|
67
|
+
process.stderr.write('Could not establish bounded Gemini execution while preserving system settings\n')
|
|
68
|
+
process.exitCode = 1
|
|
69
|
+
}
|
|
70
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hecer/yoke",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "One harness, three agents, zero trust in \"done\" — cross-agent coding harness for Claude Code, Codex CLI, and Gemini CLI: one skill canon, mechanical safety gates, an autonomous loop with screenshot/video proofs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|