bosun 0.31.6 → 0.31.7
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/.env.example +8 -2
- package/bosun.schema.json +99 -5
- package/config.mjs +198 -3
- package/container-runner.mjs +16 -3
- package/monitor.mjs +320 -42
- package/package.json +1 -1
- package/primary-agent.mjs +58 -0
- package/task-complexity.mjs +80 -0
- package/task-executor.mjs +170 -5
- package/ui/app.js +119 -97
- package/ui/components/chat-view.js +37 -6
- package/ui/components/forms.js +9 -6
- package/ui/components/shared.js +5 -11
- package/ui/demo.html +58 -0
- package/ui/modules/settings-schema.js +2 -1
- package/ui/modules/state.js +21 -13
- package/ui/modules/telegram.js +136 -63
- package/ui/styles/layout.css +8 -0
- package/ui/styles/sessions.css +10 -3
- package/ui/tabs/dashboard.js +8 -1
- package/ui/tabs/settings.js +46 -26
- package/ui/tabs/tasks.js +366 -6
- package/ui-server.mjs +379 -1
package/.env.example
CHANGED
|
@@ -244,8 +244,8 @@ TELEGRAM_MINIAPP_ENABLED=false
|
|
|
244
244
|
|
|
245
245
|
# ─── Executor Configuration ──────────────────────────────────────────────────
|
|
246
246
|
# Define AI executors that work on tasks.
|
|
247
|
-
# Format: EXECUTOR_TYPE:VARIANT:WEIGHT,EXECUTOR_TYPE:VARIANT:WEIGHT
|
|
248
|
-
# Example: COPILOT:CLAUDE_OPUS_4_6:50,CODEX:DEFAULT:50
|
|
247
|
+
# Format: EXECUTOR_TYPE:VARIANT:WEIGHT[:MODEL|MODEL],EXECUTOR_TYPE:VARIANT:WEIGHT[:MODEL|MODEL]
|
|
248
|
+
# Example: COPILOT:CLAUDE_OPUS_4_6:50:claude-opus-4.6,CODEX:DEFAULT:50:gpt-5.2-codex|gpt-5.1-codex-mini
|
|
249
249
|
# For full config, use bosun.config.json instead.
|
|
250
250
|
# EXECUTORS=CODEX:DEFAULT:100
|
|
251
251
|
|
|
@@ -897,6 +897,12 @@ COPILOT_CLOUD_DISABLED=true
|
|
|
897
897
|
# Task planner status stream interval (milliseconds). Default: 1800000 (30 min)
|
|
898
898
|
# DEVMODE_TASK_PLANNER_STATUS_INTERVAL_MS=1800000
|
|
899
899
|
|
|
900
|
+
# ─── Trigger-Based Task System ───────────────────────────────────────────────
|
|
901
|
+
# Enable configurable trigger templates (disabled by default for safety).
|
|
902
|
+
# Built-in templates ship disabled: task-planner, daily-review-digest,
|
|
903
|
+
# stale-task-followup. Configure/enable in bosun.config.json under triggerSystem.
|
|
904
|
+
# TASK_TRIGGER_SYSTEM_ENABLED=false
|
|
905
|
+
|
|
900
906
|
# ─── GitHub Issue Reconciler ─────────────────────────────────────────────────
|
|
901
907
|
# Periodically reconciles open GitHub issues against open/merged PRs.
|
|
902
908
|
# Hybrid close policy:
|
package/bosun.schema.json
CHANGED
|
@@ -88,7 +88,10 @@
|
|
|
88
88
|
"description": "Map internal statuses to project board column names",
|
|
89
89
|
"properties": {
|
|
90
90
|
"todo": { "type": "string", "default": "Todo" },
|
|
91
|
-
"inprogress": {
|
|
91
|
+
"inprogress": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"default": "In Progress"
|
|
94
|
+
},
|
|
92
95
|
"inreview": { "type": "string", "default": "In Review" },
|
|
93
96
|
"done": { "type": "string", "default": "Done" },
|
|
94
97
|
"cancelled": { "type": "string", "default": "Cancelled" }
|
|
@@ -221,6 +224,81 @@
|
|
|
221
224
|
"type": "string",
|
|
222
225
|
"enum": ["codex-sdk", "kanban", "disabled"]
|
|
223
226
|
},
|
|
227
|
+
"triggerSystem": {
|
|
228
|
+
"type": "object",
|
|
229
|
+
"additionalProperties": true,
|
|
230
|
+
"properties": {
|
|
231
|
+
"enabled": { "type": "boolean", "default": false },
|
|
232
|
+
"templates": {
|
|
233
|
+
"type": "array",
|
|
234
|
+
"items": {
|
|
235
|
+
"type": "object",
|
|
236
|
+
"additionalProperties": true,
|
|
237
|
+
"properties": {
|
|
238
|
+
"id": { "type": "string" },
|
|
239
|
+
"name": { "type": "string" },
|
|
240
|
+
"description": { "type": "string" },
|
|
241
|
+
"enabled": { "type": "boolean", "default": false },
|
|
242
|
+
"action": {
|
|
243
|
+
"type": "string",
|
|
244
|
+
"enum": ["task-planner", "create-task"]
|
|
245
|
+
},
|
|
246
|
+
"minIntervalMinutes": { "type": "number", "minimum": 1 },
|
|
247
|
+
"trigger": {
|
|
248
|
+
"type": "object",
|
|
249
|
+
"additionalProperties": true,
|
|
250
|
+
"properties": {
|
|
251
|
+
"anyOf": {
|
|
252
|
+
"type": "array",
|
|
253
|
+
"items": {
|
|
254
|
+
"type": "object",
|
|
255
|
+
"additionalProperties": true,
|
|
256
|
+
"properties": {
|
|
257
|
+
"kind": {
|
|
258
|
+
"type": "string",
|
|
259
|
+
"enum": ["metric", "interval"]
|
|
260
|
+
},
|
|
261
|
+
"metric": { "type": "string" },
|
|
262
|
+
"operator": {
|
|
263
|
+
"type": "string",
|
|
264
|
+
"enum": ["lt", "lte", "gt", "gte", "eq", "neq"]
|
|
265
|
+
},
|
|
266
|
+
"value": { "type": ["number", "string", "boolean"] },
|
|
267
|
+
"minutes": { "type": "number", "minimum": 1 }
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
"config": {
|
|
274
|
+
"type": "object",
|
|
275
|
+
"additionalProperties": true,
|
|
276
|
+
"properties": {
|
|
277
|
+
"plannerMode": {
|
|
278
|
+
"type": "string",
|
|
279
|
+
"enum": ["codex-sdk", "kanban", "disabled"]
|
|
280
|
+
},
|
|
281
|
+
"defaultTaskCount": { "type": "number", "minimum": 1 },
|
|
282
|
+
"title": { "type": "string" },
|
|
283
|
+
"description": { "type": "string" },
|
|
284
|
+
"priority": { "type": "string" },
|
|
285
|
+
"executor": { "type": "string" },
|
|
286
|
+
"model": { "type": "string" }
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
"defaults": {
|
|
293
|
+
"type": "object",
|
|
294
|
+
"additionalProperties": false,
|
|
295
|
+
"properties": {
|
|
296
|
+
"executor": { "type": "string" },
|
|
297
|
+
"model": { "type": "string" }
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
},
|
|
224
302
|
"activeWorkspace": {
|
|
225
303
|
"type": "string",
|
|
226
304
|
"description": "ID of the currently active workspace"
|
|
@@ -249,11 +327,23 @@
|
|
|
249
327
|
"additionalProperties": true,
|
|
250
328
|
"required": ["name"],
|
|
251
329
|
"properties": {
|
|
252
|
-
"name": {
|
|
330
|
+
"name": {
|
|
331
|
+
"type": "string",
|
|
332
|
+
"description": "Repository directory name"
|
|
333
|
+
},
|
|
253
334
|
"url": { "type": "string", "description": "Git clone URL" },
|
|
254
|
-
"slug": {
|
|
255
|
-
|
|
256
|
-
|
|
335
|
+
"slug": {
|
|
336
|
+
"type": "string",
|
|
337
|
+
"description": "GitHub slug (org/repo)"
|
|
338
|
+
},
|
|
339
|
+
"primary": {
|
|
340
|
+
"type": "boolean",
|
|
341
|
+
"description": "Whether this is the primary repo"
|
|
342
|
+
},
|
|
343
|
+
"branch": {
|
|
344
|
+
"type": "string",
|
|
345
|
+
"description": "Default branch to track"
|
|
346
|
+
}
|
|
257
347
|
}
|
|
258
348
|
}
|
|
259
349
|
},
|
|
@@ -399,6 +489,10 @@
|
|
|
399
489
|
"name": { "type": "string" },
|
|
400
490
|
"executor": { "type": "string" },
|
|
401
491
|
"variant": { "type": "string" },
|
|
492
|
+
"models": {
|
|
493
|
+
"type": "array",
|
|
494
|
+
"items": { "type": "string" }
|
|
495
|
+
},
|
|
402
496
|
"weight": { "type": "number" },
|
|
403
497
|
"role": { "type": "string" },
|
|
404
498
|
"enabled": { "type": "boolean" }
|
package/config.mjs
CHANGED
|
@@ -26,6 +26,10 @@ import {
|
|
|
26
26
|
} from "./agent-prompts.mjs";
|
|
27
27
|
import { resolveAgentRepoRoot } from "./repo-root.mjs";
|
|
28
28
|
import { applyAllCompatibility } from "./compat.mjs";
|
|
29
|
+
import {
|
|
30
|
+
normalizeExecutorKey,
|
|
31
|
+
getModelsForExecutor,
|
|
32
|
+
} from "./task-complexity.mjs";
|
|
29
33
|
|
|
30
34
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
31
35
|
|
|
@@ -268,6 +272,173 @@ function isEnvEnabled(value, defaultValue = false) {
|
|
|
268
272
|
return parseEnvBoolean(value, defaultValue);
|
|
269
273
|
}
|
|
270
274
|
|
|
275
|
+
function parseListValue(value) {
|
|
276
|
+
if (Array.isArray(value)) {
|
|
277
|
+
return value
|
|
278
|
+
.map((item) => String(item || "").trim())
|
|
279
|
+
.filter(Boolean);
|
|
280
|
+
}
|
|
281
|
+
return String(value || "")
|
|
282
|
+
.split(/[,|]/)
|
|
283
|
+
.map((item) => item.trim())
|
|
284
|
+
.filter(Boolean);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function normalizeExecutorModels(executor, models) {
|
|
288
|
+
const normalizedExecutor = normalizeExecutorKey(executor);
|
|
289
|
+
if (!normalizedExecutor) return [];
|
|
290
|
+
const input = parseListValue(models);
|
|
291
|
+
const known = new Set(getModelsForExecutor(normalizedExecutor));
|
|
292
|
+
if (input.length === 0) {
|
|
293
|
+
return [...known];
|
|
294
|
+
}
|
|
295
|
+
return input.filter((model) => known.has(model));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function normalizeExecutorEntry(entry, index = 0, total = 1) {
|
|
299
|
+
if (!entry || typeof entry !== "object") return null;
|
|
300
|
+
const executorType = String(entry.executor || "").trim().toUpperCase();
|
|
301
|
+
if (!executorType) return null;
|
|
302
|
+
const variant = String(entry.variant || "DEFAULT").trim() || "DEFAULT";
|
|
303
|
+
const normalized = normalizeExecutorKey(executorType) || "codex";
|
|
304
|
+
const weight = Number(entry.weight);
|
|
305
|
+
const safeWeight = Number.isFinite(weight) ? weight : Math.floor(100 / Math.max(1, total));
|
|
306
|
+
const role =
|
|
307
|
+
String(entry.role || "").trim() ||
|
|
308
|
+
(index === 0 ? "primary" : index === 1 ? "backup" : `executor-${index + 1}`);
|
|
309
|
+
const name =
|
|
310
|
+
String(entry.name || "").trim() ||
|
|
311
|
+
`${normalized}-${String(variant || "default").toLowerCase()}`;
|
|
312
|
+
const models = normalizeExecutorModels(executorType, entry.models);
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
name,
|
|
316
|
+
executor: executorType,
|
|
317
|
+
variant,
|
|
318
|
+
weight: safeWeight,
|
|
319
|
+
role,
|
|
320
|
+
enabled: entry.enabled !== false,
|
|
321
|
+
models,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function buildDefaultTriggerTemplates({
|
|
326
|
+
plannerMode,
|
|
327
|
+
plannerPerCapitaThreshold,
|
|
328
|
+
plannerIdleSlotThreshold,
|
|
329
|
+
plannerDedupHours,
|
|
330
|
+
} = {}) {
|
|
331
|
+
return [
|
|
332
|
+
{
|
|
333
|
+
id: "task-planner",
|
|
334
|
+
name: "Task Planner",
|
|
335
|
+
description: "Create planning tasks when backlog/slot metrics indicate replenishment.",
|
|
336
|
+
enabled: false,
|
|
337
|
+
action: "task-planner",
|
|
338
|
+
trigger: {
|
|
339
|
+
anyOf: [
|
|
340
|
+
{
|
|
341
|
+
kind: "metric",
|
|
342
|
+
metric: "backlogPerCapita",
|
|
343
|
+
operator: "lt",
|
|
344
|
+
value: plannerPerCapitaThreshold,
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
kind: "metric",
|
|
348
|
+
metric: "idleSlots",
|
|
349
|
+
operator: "gte",
|
|
350
|
+
value: plannerIdleSlotThreshold,
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
kind: "metric",
|
|
354
|
+
metric: "backlogRemaining",
|
|
355
|
+
operator: "eq",
|
|
356
|
+
value: 0,
|
|
357
|
+
},
|
|
358
|
+
],
|
|
359
|
+
},
|
|
360
|
+
minIntervalMinutes: Math.max(1, Number(plannerDedupHours || 6) * 60),
|
|
361
|
+
config: {
|
|
362
|
+
plannerMode,
|
|
363
|
+
defaultTaskCount: Number(process.env.TASK_PLANNER_DEFAULT_COUNT || "30"),
|
|
364
|
+
executor: "auto",
|
|
365
|
+
model: "auto",
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
id: "daily-review-digest",
|
|
370
|
+
name: "Daily Review Digest",
|
|
371
|
+
description: "Create a daily review task for fleet health and backlog quality.",
|
|
372
|
+
enabled: false,
|
|
373
|
+
action: "create-task",
|
|
374
|
+
trigger: {
|
|
375
|
+
anyOf: [
|
|
376
|
+
{
|
|
377
|
+
kind: "interval",
|
|
378
|
+
minutes: 24 * 60,
|
|
379
|
+
},
|
|
380
|
+
],
|
|
381
|
+
},
|
|
382
|
+
minIntervalMinutes: 24 * 60,
|
|
383
|
+
config: {
|
|
384
|
+
title: "[m] Daily review digest",
|
|
385
|
+
description:
|
|
386
|
+
"Review active backlog, blocked tasks, and stale work. Capture next actions and priority adjustments.",
|
|
387
|
+
priority: "medium",
|
|
388
|
+
executor: "auto",
|
|
389
|
+
model: "auto",
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
id: "stale-task-followup",
|
|
394
|
+
name: "Stale Task Follow-up",
|
|
395
|
+
description: "Create a follow-up task when stale in-progress work accumulates.",
|
|
396
|
+
enabled: false,
|
|
397
|
+
action: "create-task",
|
|
398
|
+
trigger: {
|
|
399
|
+
anyOf: [
|
|
400
|
+
{
|
|
401
|
+
kind: "metric",
|
|
402
|
+
metric: "staleInProgressCount",
|
|
403
|
+
operator: "gte",
|
|
404
|
+
value: 1,
|
|
405
|
+
},
|
|
406
|
+
],
|
|
407
|
+
},
|
|
408
|
+
minIntervalMinutes: 60,
|
|
409
|
+
config: {
|
|
410
|
+
title: "[m] Follow up stale in-progress tasks",
|
|
411
|
+
description:
|
|
412
|
+
"Audit stale in-progress tasks, unblock owners, or split work to recover flow.",
|
|
413
|
+
priority: "high",
|
|
414
|
+
staleHours: Number(process.env.STALE_TASK_AGE_HOURS || "24"),
|
|
415
|
+
executor: "auto",
|
|
416
|
+
model: "auto",
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
];
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function resolveTriggerSystemConfig(configData, defaults) {
|
|
423
|
+
const configTrigger =
|
|
424
|
+
configData && typeof configData.triggerSystem === "object"
|
|
425
|
+
? configData.triggerSystem
|
|
426
|
+
: configData && typeof configData.triggers === "object"
|
|
427
|
+
? configData.triggers
|
|
428
|
+
: {};
|
|
429
|
+
const templates = Array.isArray(configTrigger.templates)
|
|
430
|
+
? configTrigger.templates
|
|
431
|
+
: defaults.templates;
|
|
432
|
+
return Object.freeze({
|
|
433
|
+
enabled: isEnvEnabled(
|
|
434
|
+
process.env.TASK_TRIGGER_SYSTEM_ENABLED ?? configTrigger.enabled,
|
|
435
|
+
false,
|
|
436
|
+
),
|
|
437
|
+
templates,
|
|
438
|
+
defaults: defaults.defaults,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
|
|
271
442
|
// ── Git helpers ──────────────────────────────────────────────────────────────
|
|
272
443
|
|
|
273
444
|
function detectRepoSlug(repoRoot = "") {
|
|
@@ -421,7 +592,7 @@ const DEFAULT_EXECUTORS = {
|
|
|
421
592
|
};
|
|
422
593
|
|
|
423
594
|
function parseExecutorsFromEnv() {
|
|
424
|
-
// EXECUTORS=CODEX:DEFAULT:100
|
|
595
|
+
// EXECUTORS=CODEX:DEFAULT:100:gpt-5.2-codex|gpt-5.1-codex-mini
|
|
425
596
|
const raw = process.env.EXECUTORS;
|
|
426
597
|
if (!raw) return null;
|
|
427
598
|
const entries = raw.split(",").map((e) => e.trim());
|
|
@@ -430,13 +601,16 @@ function parseExecutorsFromEnv() {
|
|
|
430
601
|
for (let i = 0; i < entries.length; i++) {
|
|
431
602
|
const parts = entries[i].split(":");
|
|
432
603
|
if (parts.length < 2) continue;
|
|
604
|
+
const executorType = parts[0].toUpperCase();
|
|
605
|
+
const models = normalizeExecutorModels(executorType, parts[3] || "");
|
|
433
606
|
executors.push({
|
|
434
607
|
name: `${parts[0].toLowerCase()}-${parts[1].toLowerCase()}`,
|
|
435
|
-
executor:
|
|
608
|
+
executor: executorType,
|
|
436
609
|
variant: parts[1],
|
|
437
610
|
weight: parts[2] ? Number(parts[2]) : Math.floor(100 / entries.length),
|
|
438
611
|
role: roles[i] || `executor-${i + 1}`,
|
|
439
612
|
enabled: true,
|
|
613
|
+
models,
|
|
440
614
|
});
|
|
441
615
|
}
|
|
442
616
|
return executors.length ? executors : null;
|
|
@@ -522,8 +696,11 @@ function loadExecutorConfig(configDir, configData) {
|
|
|
522
696
|
}
|
|
523
697
|
}
|
|
524
698
|
|
|
525
|
-
const
|
|
699
|
+
const baseExecutors =
|
|
526
700
|
fromEnv || fromFile?.executors || DEFAULT_EXECUTORS.executors;
|
|
701
|
+
const executors = (Array.isArray(baseExecutors) ? baseExecutors : [])
|
|
702
|
+
.map((entry, index, arr) => normalizeExecutorEntry(entry, index, arr.length))
|
|
703
|
+
.filter(Boolean);
|
|
527
704
|
const failover = fromFile?.failover || {
|
|
528
705
|
strategy:
|
|
529
706
|
process.env.FAILOVER_STRATEGY || DEFAULT_EXECUTORS.failover.strategy,
|
|
@@ -1483,6 +1660,23 @@ export function loadConfig(argv = process.argv, options = {}) {
|
|
|
1483
1660
|
? plannerDedupHours * 60 * 60 * 1000
|
|
1484
1661
|
: 24 * 60 * 60 * 1000;
|
|
1485
1662
|
|
|
1663
|
+
const triggerSystemDefaults = Object.freeze({
|
|
1664
|
+
templates: buildDefaultTriggerTemplates({
|
|
1665
|
+
plannerMode,
|
|
1666
|
+
plannerPerCapitaThreshold,
|
|
1667
|
+
plannerIdleSlotThreshold,
|
|
1668
|
+
plannerDedupHours,
|
|
1669
|
+
}),
|
|
1670
|
+
defaults: Object.freeze({
|
|
1671
|
+
executor: "auto",
|
|
1672
|
+
model: "auto",
|
|
1673
|
+
}),
|
|
1674
|
+
});
|
|
1675
|
+
const triggerSystem = resolveTriggerSystemConfig(
|
|
1676
|
+
configData,
|
|
1677
|
+
triggerSystemDefaults,
|
|
1678
|
+
);
|
|
1679
|
+
|
|
1486
1680
|
// ── GitHub Reconciler ───────────────────────────────────
|
|
1487
1681
|
const ghReconcileEnabled = isEnvEnabled(
|
|
1488
1682
|
process.env.GH_RECONCILE_ENABLED ?? configData.ghReconcileEnabled,
|
|
@@ -1749,6 +1943,7 @@ export function loadConfig(argv = process.argv, options = {}) {
|
|
|
1749
1943
|
plannerIdleSlotThreshold,
|
|
1750
1944
|
plannerDedupHours,
|
|
1751
1945
|
plannerDedupMs,
|
|
1946
|
+
triggerSystem,
|
|
1752
1947
|
|
|
1753
1948
|
// GitHub Reconciler
|
|
1754
1949
|
githubReconcile: {
|
package/container-runner.mjs
CHANGED
|
@@ -31,6 +31,10 @@ const containerTimeout = parseInt(
|
|
|
31
31
|
process.env.CONTAINER_TIMEOUT_MS || "1800000",
|
|
32
32
|
10,
|
|
33
33
|
); // 30 min default
|
|
34
|
+
const containerRuntimeCheckTimeout = Math.max(
|
|
35
|
+
500,
|
|
36
|
+
parseInt(process.env.CONTAINER_RUNTIME_CHECK_TIMEOUT_MS || "3000", 10),
|
|
37
|
+
);
|
|
34
38
|
const containerMaxOutput = parseInt(
|
|
35
39
|
process.env.CONTAINER_MAX_OUTPUT_SIZE || "10485760",
|
|
36
40
|
10,
|
|
@@ -84,11 +88,17 @@ export function checkContainerRuntime() {
|
|
|
84
88
|
try {
|
|
85
89
|
if (containerRuntime === "container") {
|
|
86
90
|
// macOS Apple Container
|
|
87
|
-
execSync("container system status", {
|
|
91
|
+
execSync("container system status", {
|
|
92
|
+
stdio: "pipe",
|
|
93
|
+
timeout: containerRuntimeCheckTimeout,
|
|
94
|
+
});
|
|
88
95
|
return { available: true, runtime: "container", platform: "macos" };
|
|
89
96
|
}
|
|
90
97
|
// Docker or Podman
|
|
91
|
-
execSync(`${containerRuntime} info`, {
|
|
98
|
+
execSync(`${containerRuntime} info`, {
|
|
99
|
+
stdio: "pipe",
|
|
100
|
+
timeout: containerRuntimeCheckTimeout,
|
|
101
|
+
});
|
|
92
102
|
return {
|
|
93
103
|
available: true,
|
|
94
104
|
runtime: containerRuntime,
|
|
@@ -110,7 +120,10 @@ export function ensureContainerRuntime() {
|
|
|
110
120
|
if (containerRuntime === "container") {
|
|
111
121
|
// macOS Apple Container — may need explicit start
|
|
112
122
|
try {
|
|
113
|
-
execSync("container system status", {
|
|
123
|
+
execSync("container system status", {
|
|
124
|
+
stdio: "pipe",
|
|
125
|
+
timeout: containerRuntimeCheckTimeout,
|
|
126
|
+
});
|
|
114
127
|
} catch {
|
|
115
128
|
console.log("[container] Starting Apple Container system...");
|
|
116
129
|
try {
|