@opengoat/core 2026.2.18-2 → 2026.2.19

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.
Files changed (29) hide show
  1. package/dist/core/agents/application/agent.service.d.ts +2 -0
  2. package/dist/core/agents/application/agent.service.js +32 -9
  3. package/dist/core/agents/application/agent.service.js.map +1 -1
  4. package/dist/core/boards/application/board.service.d.ts +5 -1
  5. package/dist/core/boards/application/board.service.js +115 -29
  6. package/dist/core/boards/application/board.service.js.map +1 -1
  7. package/dist/core/boards/domain/board.d.ts +1 -2
  8. package/dist/core/opengoat/application/opengoat.service.d.ts +6 -1
  9. package/dist/core/opengoat/application/opengoat.service.helpers.d.ts +52 -0
  10. package/dist/core/opengoat/application/opengoat.service.helpers.js +129 -3
  11. package/dist/core/opengoat/application/opengoat.service.helpers.js.map +1 -1
  12. package/dist/core/opengoat/application/opengoat.service.js +229 -45
  13. package/dist/core/opengoat/application/opengoat.service.js.map +1 -1
  14. package/dist/core/orchestration/application/orchestration.service.js +6 -1
  15. package/dist/core/orchestration/application/orchestration.service.js.map +1 -1
  16. package/dist/core/providers/application/provider.service.d.ts +3 -0
  17. package/dist/core/providers/application/provider.service.js +126 -0
  18. package/dist/core/providers/application/provider.service.js.map +1 -1
  19. package/dist/core/providers/cli-provider.d.ts +1 -0
  20. package/dist/core/providers/cli-provider.js +11 -8
  21. package/dist/core/providers/cli-provider.js.map +1 -1
  22. package/dist/core/providers/openclaw-gateway-rpc.js +70 -17
  23. package/dist/core/providers/openclaw-gateway-rpc.js.map +1 -1
  24. package/dist/core/providers/providers/codex/provider.d.ts +1 -0
  25. package/dist/core/providers/providers/codex/provider.js +24 -0
  26. package/dist/core/providers/providers/codex/provider.js.map +1 -1
  27. package/dist/core/providers/types.d.ts +1 -0
  28. package/dist/core/templates/assets/skills/og-board-manager/SKILL.md +3 -6
  29. package/package.json +1 -1
@@ -10,11 +10,12 @@ import { ProviderCommandNotFoundError, createDefaultProviderRegistry, } from "..
10
10
  import { dirname, resolve as resolvePath } from "node:path";
11
11
  import { SessionService, } from "../../sessions/index.js";
12
12
  import { SkillService, } from "../../skills/index.js";
13
- import { assertAgentExists, buildBlockedTaskMessage, buildInactiveAgentsMessage, buildNotificationSessionRef, buildPendingTaskMessage, buildReporteeStats, buildTodoTaskMessage, collectAllReportees, containsAgentNotFoundMessage, containsAlreadyExistsMessage, extractManagedSkillsDir, extractOpenClawAgents, isSpawnPermissionOrMissing, pathIsWithin, pathMatches, prepareOpenClawCommandEnv, resolveInactiveAgentNotificationTarget, resolveInactiveMinutes, resolveMaxParallelFlows, toErrorMessage, } from "./opengoat.service.helpers.js";
13
+ import { assertAgentExists, buildBlockedTaskMessage, buildDoingTaskMessage, buildInactiveAgentsMessage, buildNotificationSessionRef, buildPendingTaskMessage, buildReporteeStats, buildTopDownTaskDelegationMessage, buildTodoTaskMessage, collectAllReportees, containsAgentNotFoundMessage, containsAlreadyExistsMessage, extractManagedSkillsDir, extractOpenClawAgents, isSpawnPermissionOrMissing, pathIsWithin, pathMatches, prepareOpenClawCommandEnv, resolveInProgressTimeoutMinutes, resolveBottomUpTaskDelegationStrategy, resolveTopDownTaskDelegationStrategy, resolveMaxParallelFlows, toErrorMessage, } from "./opengoat.service.helpers.js";
14
14
  const OPENCLAW_PROVIDER_ID = "openclaw";
15
15
  const OPENCLAW_DEFAULT_AGENT_ID = "main";
16
16
  const OPENCLAW_AGENT_SANDBOX_MODE = "off";
17
17
  const OPENCLAW_AGENT_TOOLS_ALLOW_ALL_JSON = "[\"*\"]";
18
+ const OPENCLAW_AGENT_SKIP_BOOTSTRAP = true;
18
19
  const OPENCLAW_OPENGOAT_PLUGIN_ID = "openclaw-plugin";
19
20
  const OPENCLAW_OPENGOAT_PLUGIN_ROOT_ID = "opengoat-plugin";
20
21
  const OPENCLAW_OPENGOAT_PLUGIN_LEGACY_PACK_ID = "openclaw-plugin-pack";
@@ -205,23 +206,31 @@ export class OpenGoatService {
205
206
  warnings.push(`OpenClaw role skill assignment sync for "ceo" failed: ${toErrorMessage(error)}`);
206
207
  }
207
208
  let openClawAgentEntriesById;
209
+ let openClawInventoryAvailable = false;
208
210
  try {
209
211
  openClawAgentEntriesById = new Map((await this.listOpenClawAgents(paths)).map((entry) => [entry.id, entry]));
212
+ openClawInventoryAvailable = true;
210
213
  }
211
214
  catch (error) {
212
215
  warnings.push(`OpenClaw startup inventory check failed: ${toErrorMessage(error)}`);
213
216
  }
214
217
  try {
215
218
  localAgents = await this.agentService.listAgents(paths);
216
- for (const agent of localAgents) {
217
- const sync = await this.syncOpenClawAgentRegistration(paths, {
218
- descriptor: agent,
219
- existingEntry: openClawAgentEntriesById?.get(agent.id),
220
- });
221
- warnings.push(...sync.warnings);
222
- if (agent.id === DEFAULT_AGENT_ID) {
223
- ceoSynced = sync.synced;
224
- ceoSyncCode = sync.code;
219
+ if (!openClawInventoryAvailable) {
220
+ warnings.push("OpenClaw startup registration sync skipped because agent inventory is unavailable.");
221
+ ceoSynced = localAgents.some((agent) => agent.id === DEFAULT_AGENT_ID);
222
+ }
223
+ else {
224
+ for (const agent of localAgents) {
225
+ const sync = await this.syncOpenClawAgentRegistration(paths, {
226
+ descriptor: agent,
227
+ existingEntry: openClawAgentEntriesById?.get(agent.id),
228
+ });
229
+ warnings.push(...sync.warnings);
230
+ if (agent.id === DEFAULT_AGENT_ID) {
231
+ ceoSynced = sync.synced;
232
+ ceoSyncCode = sync.code;
233
+ }
225
234
  }
226
235
  }
227
236
  }
@@ -308,24 +317,23 @@ export class OpenGoatService {
308
317
  throw new Error(`OpenClaw agent location sync failed for "${created.agent.id}". ${toErrorMessage(error)}`);
309
318
  }
310
319
  await this.syncOpenClawAgentExecutionPolicies(paths, [created.agent.id]);
311
- try {
312
- const workspaceBootstrap = await this.agentService.ensureAgentWorkspaceBootstrap(paths, {
313
- agentId: created.agent.id,
314
- displayName: created.agent.displayName,
315
- role: options.role?.trim() ??
316
- (created.alreadyExisted ? created.agent.role : ""),
317
- }, {
318
- syncBootstrapMarkdown: false,
319
- });
320
- created.createdPaths.push(...workspaceBootstrap.createdPaths);
321
- created.skippedPaths.push(...workspaceBootstrap.skippedPaths);
322
- created.skippedPaths.push(...workspaceBootstrap.removedPaths);
323
- }
324
- catch (error) {
325
- if (!created.alreadyExisted) {
320
+ if (!created.alreadyExisted) {
321
+ try {
322
+ const workspaceBootstrap = await this.agentService.ensureAgentWorkspaceBootstrap(paths, {
323
+ agentId: created.agent.id,
324
+ displayName: created.agent.displayName,
325
+ role: options.role?.trim() ?? "",
326
+ }, {
327
+ syncBootstrapMarkdown: false,
328
+ });
329
+ created.createdPaths.push(...workspaceBootstrap.createdPaths);
330
+ created.skippedPaths.push(...workspaceBootstrap.skippedPaths);
331
+ created.skippedPaths.push(...workspaceBootstrap.removedPaths);
332
+ }
333
+ catch (error) {
326
334
  await this.agentService.removeAgent(paths, created.agent.id);
335
+ throw new Error(`Failed to update workspace bootstrap for "${created.agent.id}". ${toErrorMessage(error)}`);
327
336
  }
328
- throw new Error(`Failed to update workspace bootstrap for "${created.agent.id}". ${toErrorMessage(error)}`);
329
337
  }
330
338
  return {
331
339
  ...created,
@@ -555,18 +563,23 @@ export class OpenGoatService {
555
563
  const paths = this.pathsProvider.getPaths();
556
564
  const ranAt = this.resolveNowIso();
557
565
  const manifests = await this.agentManifestService.listManifests(paths);
558
- const inactiveMinutes = resolveInactiveMinutes(options.inactiveMinutes);
559
- const notificationTarget = resolveInactiveAgentNotificationTarget(options.notificationTarget);
560
- const notifyInactiveAgents = options.notifyInactiveAgents ?? true;
566
+ const inProgressMinutes = resolveInProgressTimeoutMinutes(options.inProgressMinutes);
567
+ const topDownStrategy = resolveTopDownTaskDelegationStrategy(options);
568
+ const bottomUpStrategy = resolveBottomUpTaskDelegationStrategy(options);
561
569
  const maxParallelFlows = resolveMaxParallelFlows(options.maxParallelFlows);
562
- const inactiveCandidates = notifyInactiveAgents
563
- ? await this.collectInactiveAgents(paths, manifests, inactiveMinutes, notificationTarget)
570
+ const inactiveCandidates = bottomUpStrategy.enabled
571
+ ? await this.collectInactiveAgents(paths, manifests, bottomUpStrategy.inactiveMinutes, bottomUpStrategy.notificationTarget)
564
572
  : [];
565
573
  const tasks = await this.boardService.listTasks(paths, { limit: 10_000 });
566
- const pendingTaskIds = new Set(await this.boardService.listPendingTaskIdsOlderThan(paths, inactiveMinutes));
567
- const taskStatusDispatch = await this.dispatchTaskStatusAutomations(paths, tasks, manifests, pendingTaskIds, inactiveMinutes, ranAt, maxParallelFlows);
568
- const inactiveDispatches = await this.dispatchInactiveAgentAutomations(paths, inactiveCandidates, inactiveMinutes, ranAt, maxParallelFlows);
574
+ const topDownDispatches = topDownStrategy.enabled
575
+ ? await this.dispatchTopDownTaskDelegationAutomations(paths, tasks, manifests, topDownStrategy.openTasksThreshold, ranAt, maxParallelFlows)
576
+ : [];
577
+ const pendingTaskIds = new Set(await this.boardService.listPendingTaskIdsOlderThan(paths, bottomUpStrategy.inactiveMinutes));
578
+ const doingTaskIds = new Set(await this.boardService.listDoingTaskIdsOlderThan(paths, inProgressMinutes));
579
+ const taskStatusDispatch = await this.dispatchTaskStatusAutomations(paths, tasks, manifests, doingTaskIds, pendingTaskIds, inProgressMinutes, bottomUpStrategy.inactiveMinutes, ranAt, maxParallelFlows);
580
+ const inactiveDispatches = await this.dispatchInactiveAgentAutomations(paths, inactiveCandidates, bottomUpStrategy.inactiveMinutes, ranAt, maxParallelFlows);
569
581
  const dispatches = [
582
+ ...topDownDispatches,
570
583
  ...taskStatusDispatch.dispatches,
571
584
  ...inactiveDispatches,
572
585
  ];
@@ -575,6 +588,7 @@ export class OpenGoatService {
575
588
  ranAt,
576
589
  scannedTasks: tasks.length,
577
590
  todoTasks: taskStatusDispatch.todoTasks,
591
+ doingTasks: taskStatusDispatch.doingTasks,
578
592
  blockedTasks: taskStatusDispatch.blockedTasks,
579
593
  inactiveAgents: inactiveCandidates.length,
580
594
  sent: dispatches.length - failed,
@@ -582,10 +596,67 @@ export class OpenGoatService {
582
596
  dispatches,
583
597
  };
584
598
  }
585
- async dispatchTaskStatusAutomations(paths, tasks, manifests, pendingTaskIds, pendingMinutes, notificationTimestamp, maxParallelFlows) {
599
+ async dispatchTopDownTaskDelegationAutomations(paths, tasks, manifests, openTasksThreshold, notificationTimestamp, maxParallelFlows = 1) {
600
+ const openTasks = tasks
601
+ .filter((task) => task.status.trim().toLowerCase() !== "done")
602
+ .sort((left, right) => {
603
+ const leftTimestamp = Date.parse(left.updatedAt);
604
+ const rightTimestamp = Date.parse(right.updatedAt);
605
+ if (Number.isFinite(leftTimestamp) && Number.isFinite(rightTimestamp)) {
606
+ return rightTimestamp - leftTimestamp;
607
+ }
608
+ if (Number.isFinite(rightTimestamp)) {
609
+ return 1;
610
+ }
611
+ if (Number.isFinite(leftTimestamp)) {
612
+ return -1;
613
+ }
614
+ return right.taskId.localeCompare(left.taskId);
615
+ });
616
+ if (openTasks.length > openTasksThreshold) {
617
+ return [];
618
+ }
619
+ const managerAgents = manifests.filter((manifest) => manifest.metadata.type === "manager").length;
620
+ const ceoDirectReportees = manifests.filter((manifest) => normalizeAgentId(manifest.metadata.reportsTo ?? "") === DEFAULT_AGENT_ID).length;
621
+ const sessionRef = buildNotificationSessionRef(DEFAULT_AGENT_ID);
622
+ const message = buildTopDownTaskDelegationMessage({
623
+ openTasksThreshold,
624
+ openTasksCount: openTasks.length,
625
+ totalAgents: manifests.length,
626
+ managerAgents,
627
+ ceoDirectReportees,
628
+ openTasks: openTasks.map((task) => ({
629
+ taskId: task.taskId,
630
+ title: task.title,
631
+ status: task.status,
632
+ assignedTo: task.assignedTo,
633
+ })),
634
+ notificationTimestamp,
635
+ });
636
+ const requests = [
637
+ {
638
+ targetAgentId: DEFAULT_AGENT_ID,
639
+ sessionRef,
640
+ message,
641
+ },
642
+ ];
643
+ return runWithConcurrencyByKey(requests, maxParallelFlows, (request) => request.targetAgentId, async (request) => {
644
+ const result = await this.dispatchAutomationMessage(paths, request.targetAgentId, request.sessionRef, request.message);
645
+ return {
646
+ kind: "topdown",
647
+ targetAgentId: request.targetAgentId,
648
+ sessionRef: request.sessionRef,
649
+ message: request.message,
650
+ ok: result.ok,
651
+ error: result.error,
652
+ };
653
+ });
654
+ }
655
+ async dispatchTaskStatusAutomations(paths, tasks, manifests, doingTaskIds, pendingTaskIds, doingMinutes, pendingMinutes, notificationTimestamp, maxParallelFlows) {
586
656
  const manifestsById = new Map(manifests.map((manifest) => [manifest.agentId, manifest]));
587
657
  const requests = [];
588
658
  let todoTasks = 0;
659
+ let doingTasks = 0;
589
660
  let pendingTasks = 0;
590
661
  let blockedTasks = 0;
591
662
  for (const task of tasks) {
@@ -606,6 +677,27 @@ export class OpenGoatService {
606
677
  });
607
678
  continue;
608
679
  }
680
+ if (task.status === "doing") {
681
+ if (!doingTaskIds.has(task.taskId)) {
682
+ continue;
683
+ }
684
+ doingTasks += 1;
685
+ const targetAgentId = task.assignedTo;
686
+ const sessionRef = buildNotificationSessionRef(targetAgentId);
687
+ const message = buildDoingTaskMessage({
688
+ task,
689
+ doingMinutes,
690
+ notificationTimestamp,
691
+ });
692
+ requests.push({
693
+ kind: "doing",
694
+ targetAgentId,
695
+ sessionRef,
696
+ taskId: task.taskId,
697
+ message,
698
+ });
699
+ continue;
700
+ }
609
701
  if (task.status === "pending") {
610
702
  if (!pendingTaskIds.has(task.taskId)) {
611
703
  continue;
@@ -658,9 +750,17 @@ export class OpenGoatService {
658
750
  error: result.error,
659
751
  };
660
752
  });
753
+ const notifiedDoingTaskIds = dedupeStrings(dispatches
754
+ .filter((dispatch) => dispatch.kind === "doing" && dispatch.ok)
755
+ .map((dispatch) => dispatch.taskId)
756
+ .filter((taskId) => typeof taskId === "string"));
757
+ await Promise.all(notifiedDoingTaskIds.map(async (taskId) => {
758
+ await this.boardService.resetTaskStatusTimeout(paths, taskId, "doing");
759
+ }));
661
760
  return {
662
761
  dispatches,
663
762
  todoTasks,
763
+ doingTasks,
664
764
  pendingTasks,
665
765
  blockedTasks,
666
766
  };
@@ -795,9 +895,9 @@ export class OpenGoatService {
795
895
  getPaths() {
796
896
  return this.pathsProvider.getPaths();
797
897
  }
798
- async dispatchAutomationMessage(paths, agentId, sessionRef, message, options = {}) {
898
+ async dispatchAutomationMessage(_paths, agentId, sessionRef, message, options = {}) {
799
899
  try {
800
- const result = await this.orchestrationService.runAgent(paths, agentId, {
900
+ const result = await this.runAgent(agentId, {
801
901
  message,
802
902
  sessionRef,
803
903
  disableSession: options.disableSession ?? false,
@@ -1162,6 +1262,12 @@ export class OpenGoatService {
1162
1262
  warnings.push(`OpenClaw tools policy sync failed for "${agentId}" (code ${toolsSet.code}). ${toolsSet.stderr.trim() || toolsSet.stdout.trim() || ""}`.trim());
1163
1263
  }
1164
1264
  }
1265
+ if (readAgentSkipBootstrap(entry) !== OPENCLAW_AGENT_SKIP_BOOTSTRAP) {
1266
+ const bootstrapSet = await this.runOpenClaw(["config", "set", `agents.list[${index}].skipBootstrap`, "true"], { env });
1267
+ if (bootstrapSet.code !== 0) {
1268
+ warnings.push(`OpenClaw bootstrap policy sync failed for "${agentId}" (code ${bootstrapSet.code}). ${bootstrapSet.stderr.trim() || bootstrapSet.stdout.trim() || ""}`.trim());
1269
+ }
1270
+ }
1165
1271
  }
1166
1272
  return warnings;
1167
1273
  }
@@ -1376,6 +1482,22 @@ function hasAgentToolsAllowAll(entry) {
1376
1482
  }
1377
1483
  return allow.some((value) => typeof value === "string" && value.trim() === "*");
1378
1484
  }
1485
+ function readAgentSkipBootstrap(entry) {
1486
+ const value = entry.skipBootstrap;
1487
+ if (typeof value === "boolean") {
1488
+ return value;
1489
+ }
1490
+ if (typeof value === "string") {
1491
+ const normalized = value.trim().toLowerCase();
1492
+ if (normalized === "true") {
1493
+ return true;
1494
+ }
1495
+ if (normalized === "false") {
1496
+ return false;
1497
+ }
1498
+ }
1499
+ return undefined;
1500
+ }
1379
1501
  function readStringArray(value) {
1380
1502
  if (!Array.isArray(value)) {
1381
1503
  return [];
@@ -1431,11 +1553,15 @@ function parseLooseJson(raw) {
1431
1553
  if (!trimmed) {
1432
1554
  return undefined;
1433
1555
  }
1434
- try {
1435
- return JSON.parse(trimmed);
1556
+ const exact = tryParseJson(trimmed);
1557
+ if (exact !== undefined) {
1558
+ return exact;
1436
1559
  }
1437
- catch {
1438
- // continue
1560
+ for (const line of trimmed.split(/\r?\n/)) {
1561
+ const parsedLine = tryParseJson(line.trim());
1562
+ if (parsedLine !== undefined) {
1563
+ return parsedLine;
1564
+ }
1439
1565
  }
1440
1566
  const starts = dedupeNumbers([
1441
1567
  trimmed.indexOf("{"),
@@ -1448,11 +1574,69 @@ function parseLooseJson(raw) {
1448
1574
  if (!candidate) {
1449
1575
  continue;
1450
1576
  }
1451
- try {
1452
- return JSON.parse(candidate);
1577
+ const parsedCandidate = tryParseJson(candidate);
1578
+ if (parsedCandidate !== undefined) {
1579
+ return parsedCandidate;
1453
1580
  }
1454
- catch {
1455
- // keep trying candidates
1581
+ const balancedCandidate = extractBalancedJsonCandidate(trimmed, startIndex);
1582
+ if (!balancedCandidate) {
1583
+ continue;
1584
+ }
1585
+ const parsedBalancedCandidate = tryParseJson(balancedCandidate);
1586
+ if (parsedBalancedCandidate !== undefined) {
1587
+ return parsedBalancedCandidate;
1588
+ }
1589
+ }
1590
+ return undefined;
1591
+ }
1592
+ function tryParseJson(raw) {
1593
+ if (!raw) {
1594
+ return undefined;
1595
+ }
1596
+ try {
1597
+ return JSON.parse(raw);
1598
+ }
1599
+ catch {
1600
+ return undefined;
1601
+ }
1602
+ }
1603
+ function extractBalancedJsonCandidate(raw, startIndex) {
1604
+ const opening = raw[startIndex];
1605
+ if (opening !== "{" && opening !== "[") {
1606
+ return undefined;
1607
+ }
1608
+ const closing = opening === "{" ? "}" : "]";
1609
+ let depth = 0;
1610
+ let inString = false;
1611
+ let escaping = false;
1612
+ for (let index = startIndex; index < raw.length; index += 1) {
1613
+ const char = raw[index];
1614
+ if (inString) {
1615
+ if (escaping) {
1616
+ escaping = false;
1617
+ }
1618
+ else if (char === "\\") {
1619
+ escaping = true;
1620
+ }
1621
+ else if (char === "\"") {
1622
+ inString = false;
1623
+ }
1624
+ continue;
1625
+ }
1626
+ if (char === "\"") {
1627
+ inString = true;
1628
+ continue;
1629
+ }
1630
+ if (char === opening) {
1631
+ depth += 1;
1632
+ continue;
1633
+ }
1634
+ if (char !== closing) {
1635
+ continue;
1636
+ }
1637
+ depth -= 1;
1638
+ if (depth === 0) {
1639
+ return raw.slice(startIndex, index + 1).trim();
1456
1640
  }
1457
1641
  }
1458
1642
  return undefined;