@chrysb/alphaclaw 0.5.6 → 0.6.0-beta.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.
Files changed (84) hide show
  1. package/bin/alphaclaw.js +6 -1
  2. package/lib/public/css/agents.css +92 -0
  3. package/lib/public/css/explorer.css +101 -0
  4. package/lib/public/css/shell.css +15 -4
  5. package/lib/public/js/app.js +69 -3
  6. package/lib/public/js/components/action-button.js +5 -0
  7. package/lib/public/js/components/agents-tab/agent-bindings-section/helpers.js +76 -0
  8. package/lib/public/js/components/agents-tab/agent-bindings-section/index.js +490 -0
  9. package/lib/public/js/components/agents-tab/agent-bindings-section/use-agent-bindings.js +256 -0
  10. package/lib/public/js/components/agents-tab/agent-detail-panel.js +74 -0
  11. package/lib/public/js/components/agents-tab/agent-identity-section.js +175 -0
  12. package/lib/public/js/components/agents-tab/agent-overview/index.js +53 -0
  13. package/lib/public/js/components/agents-tab/agent-overview/manage-card.js +44 -0
  14. package/lib/public/js/components/agents-tab/agent-overview/model-card.js +158 -0
  15. package/lib/public/js/components/agents-tab/agent-overview/use-model-card.js +169 -0
  16. package/lib/public/js/components/agents-tab/agent-overview/use-workspace-card.js +45 -0
  17. package/lib/public/js/components/agents-tab/agent-overview/workspace-card.js +47 -0
  18. package/lib/public/js/components/agents-tab/agent-pairing-section.js +265 -0
  19. package/lib/public/js/components/agents-tab/create-agent-modal.js +189 -0
  20. package/lib/public/js/components/agents-tab/create-channel-modal.js +323 -0
  21. package/lib/public/js/components/agents-tab/delete-agent-dialog.js +50 -0
  22. package/lib/public/js/components/agents-tab/edit-agent-modal.js +109 -0
  23. package/lib/public/js/components/agents-tab/index.js +148 -0
  24. package/lib/public/js/components/agents-tab/use-agents.js +89 -0
  25. package/lib/public/js/components/channel-account-status-badge.js +35 -0
  26. package/lib/public/js/components/channel-operations-panel.js +33 -0
  27. package/lib/public/js/components/channels.js +545 -60
  28. package/lib/public/js/components/envars.js +25 -4
  29. package/lib/public/js/components/general/index.js +21 -11
  30. package/lib/public/js/components/general/use-general-tab.js +78 -16
  31. package/lib/public/js/components/google/gmail-setup-wizard.js +1 -3
  32. package/lib/public/js/components/google/index.js +28 -30
  33. package/lib/public/js/components/icons.js +37 -0
  34. package/lib/public/js/components/models-tab/index.js +58 -224
  35. package/lib/public/js/components/models-tab/model-picker.js +212 -0
  36. package/lib/public/js/components/models-tab/use-models.js +17 -14
  37. package/lib/public/js/components/onboarding/use-welcome-pairing.js +4 -4
  38. package/lib/public/js/components/onboarding/welcome-pairing-step.js +2 -2
  39. package/lib/public/js/components/overflow-menu.js +122 -0
  40. package/lib/public/js/components/pairings.js +36 -8
  41. package/lib/public/js/components/routes/agents-route.js +27 -0
  42. package/lib/public/js/components/routes/general-route.js +2 -0
  43. package/lib/public/js/components/routes/index.js +1 -0
  44. package/lib/public/js/components/routes/telegram-route.js +2 -2
  45. package/lib/public/js/components/secret-input.js +8 -1
  46. package/lib/public/js/components/sidebar.js +64 -26
  47. package/lib/public/js/components/telegram-workspace/index.js +175 -74
  48. package/lib/public/js/components/telegram-workspace/manage.js +83 -10
  49. package/lib/public/js/components/telegram-workspace/onboarding.js +9 -8
  50. package/lib/public/js/components/webhooks.js +43 -18
  51. package/lib/public/js/hooks/use-app-shell-controller.js +7 -0
  52. package/lib/public/js/hooks/use-browse-navigation.js +8 -5
  53. package/lib/public/js/hooks/use-destination-session-selection.js +8 -1
  54. package/lib/public/js/lib/api.js +163 -9
  55. package/lib/public/js/lib/app-navigation.js +2 -1
  56. package/lib/public/js/lib/channel-create-operation.js +102 -0
  57. package/lib/public/js/lib/format.js +14 -0
  58. package/lib/public/js/lib/sse.js +51 -0
  59. package/lib/public/js/lib/telegram-api.js +38 -18
  60. package/lib/public/setup.html +1 -0
  61. package/lib/public/shared/browse-file-policies.json +0 -1
  62. package/lib/server/agents/service.js +1478 -0
  63. package/lib/server/constants.js +2 -2
  64. package/lib/server/env.js +3 -1
  65. package/lib/server/gateway.js +104 -20
  66. package/lib/server/gmail-watch.js +29 -2
  67. package/lib/server/onboarding/import/import-applier.js +0 -1
  68. package/lib/server/onboarding/index.js +0 -6
  69. package/lib/server/onboarding/workspace.js +73 -38
  70. package/lib/server/openclaw-config.js +23 -0
  71. package/lib/server/operation-events.js +141 -0
  72. package/lib/server/routes/agents.js +266 -0
  73. package/lib/server/routes/pairings.js +135 -25
  74. package/lib/server/routes/system.js +90 -10
  75. package/lib/server/routes/telegram.js +247 -51
  76. package/lib/server/telegram-workspace.js +61 -10
  77. package/lib/server/topic-registry.js +66 -7
  78. package/lib/server/watchdog.js +39 -1
  79. package/lib/server/webhooks.js +60 -12
  80. package/lib/server.js +21 -7
  81. package/lib/setup/core-prompts/AGENTS.md +6 -5
  82. package/lib/setup/core-prompts/TOOLS.md +1 -8
  83. package/package.json +1 -1
  84. package/lib/setup/skills/control-ui/SKILL.md +0 -62
@@ -2,6 +2,14 @@ const fs = require("fs");
2
2
  const { WORKSPACE_DIR } = require("./constants");
3
3
 
4
4
  const kRegistryPath = `${WORKSPACE_DIR}/topic-registry.json`;
5
+ const kDefaultAccountId = "default";
6
+ const kDefaultAgentId = "default";
7
+
8
+ const normalizeAccountId = (value) =>
9
+ String(value || "").trim() || kDefaultAccountId;
10
+
11
+ const normalizeGroupAgentId = (value) =>
12
+ String(value || "").trim() || kDefaultAgentId;
5
13
 
6
14
  const readRegistry = () => {
7
15
  try {
@@ -36,6 +44,20 @@ const setGroup = (groupId, groupData) => {
36
44
  return registry;
37
45
  };
38
46
 
47
+ const getGroupsForAccount = (accountId) => {
48
+ const registry = readRegistry();
49
+ const normalizedAccountId = normalizeAccountId(accountId);
50
+ const groups = registry.groups && typeof registry.groups === "object"
51
+ ? registry.groups
52
+ : {};
53
+ return Object.fromEntries(
54
+ Object.entries(groups).filter(([, group]) => {
55
+ const groupAccountId = normalizeAccountId(group?.accountId);
56
+ return groupAccountId === normalizedAccountId;
57
+ }),
58
+ );
59
+ };
60
+
39
61
  const addTopic = (groupId, threadId, topicData) => {
40
62
  const registry = readRegistry();
41
63
  if (!registry.groups[groupId]) {
@@ -90,21 +112,56 @@ const getTotalTopicCount = () => {
90
112
  return count;
91
113
  };
92
114
 
93
- // Render the topic registry as a markdown section for TOOLS.md
94
- const renderTopicRegistryMarkdown = ({ includeSyncGuidance = false } = {}) => {
115
+ const getTopicsForAgent = (agentId) => {
95
116
  const registry = readRegistry();
117
+ const groups = registry.groups && typeof registry.groups === "object"
118
+ ? registry.groups
119
+ : {};
120
+ const normalizedAgentId = normalizeGroupAgentId(agentId);
96
121
  const rows = [];
97
- for (const [groupId, group] of Object.entries(registry.groups)) {
98
- for (const [threadId, topic] of Object.entries(group.topics || {})) {
122
+ for (const [groupId, group] of Object.entries(groups)) {
123
+ const groupAgentId = normalizeGroupAgentId(group?.agentId);
124
+ const groupName = String(group?.name || "").trim() || groupId;
125
+ const topics = group?.topics && typeof group.topics === "object"
126
+ ? group.topics
127
+ : {};
128
+ const isGroupOwner = groupAgentId === normalizedAgentId;
129
+ for (const [threadId, topic] of Object.entries(topics)) {
130
+ const topicAgentId = String(topic?.agentId || "").trim();
131
+ if (!isGroupOwner && topicAgentId !== normalizedAgentId) continue;
99
132
  rows.push({
100
- groupName: group.name || groupId,
133
+ groupName,
101
134
  groupId,
102
- topicName: topic.name,
135
+ topicName: topic?.name,
103
136
  threadId,
137
+ groupAgentId,
138
+ topicAgentId,
104
139
  });
105
140
  }
106
141
  }
107
- if (rows.length === 0 && !includeSyncGuidance) return "";
142
+ return rows;
143
+ };
144
+
145
+ // Render the topic registry as a markdown section for TOOLS.md
146
+ const renderTopicRegistryMarkdown = ({
147
+ includeSyncGuidance = false,
148
+ agentId = "",
149
+ } = {}) => {
150
+ const registry = readRegistry();
151
+ const groups = registry.groups && typeof registry.groups === "object"
152
+ ? registry.groups
153
+ : {};
154
+ const normalizedAgentId = String(agentId || "").trim();
155
+ const rows = normalizedAgentId
156
+ ? getTopicsForAgent(normalizedAgentId)
157
+ : Object.entries(groups).flatMap(([groupId, group]) =>
158
+ Object.entries(group.topics || {}).map(([threadId, topic]) => ({
159
+ groupName: group.name || groupId,
160
+ groupId,
161
+ topicName: topic.name,
162
+ threadId,
163
+ })));
164
+ if (rows.length === 0) return "";
108
165
 
109
166
  const lines = [
110
167
  "",
@@ -144,9 +201,11 @@ module.exports = {
144
201
  writeRegistry,
145
202
  getGroup,
146
203
  setGroup,
204
+ getGroupsForAccount,
147
205
  addTopic,
148
206
  updateTopic,
149
207
  removeTopic,
150
208
  getTotalTopicCount,
209
+ getTopicsForAgent,
151
210
  renderTopicRegistryMarkdown,
152
211
  };
@@ -44,6 +44,18 @@ const parseHealthResult = (result) => {
44
44
  }
45
45
  return { ok: true };
46
46
  };
47
+ const isDuplicateGatewayLaunchExit = ({ code, stderrTail = [] } = {}) => {
48
+ if (code !== 1) return false;
49
+ const stderrText = (Array.isArray(stderrTail) ? stderrTail : [])
50
+ .map((entry) => String(entry || ""))
51
+ .join("\n")
52
+ .toLowerCase();
53
+ if (!stderrText) return false;
54
+ return (
55
+ stderrText.includes("another gateway instance is already listening") ||
56
+ (stderrText.includes("port") && stderrText.includes("already in use"))
57
+ );
58
+ };
47
59
 
48
60
  const createWatchdog = ({
49
61
  clawCmd,
@@ -593,6 +605,7 @@ const createWatchdog = ({
593
605
  if (expectedExit && (code == null || code === 0)) {
594
606
  state.lifecycle = "restarting";
595
607
  state.health = "unknown";
608
+ state.uptimeStartedAt = null;
596
609
  state.crashRecoveryActive = false;
597
610
  markExpectedRestartWindow();
598
611
  startBootstrapHealthChecks();
@@ -605,9 +618,33 @@ const createWatchdog = ({
605
618
  );
606
619
  return;
607
620
  }
621
+ if (isDuplicateGatewayLaunchExit({ code, stderrTail })) {
622
+ state.lifecycle = "running";
623
+ state.health = "unknown";
624
+ state.crashRecoveryActive = false;
625
+ state.startupConsecutiveHealthFailures = 0;
626
+ if (!state.uptimeStartedAt) {
627
+ state.uptimeStartedAt = Date.now();
628
+ }
629
+ startBootstrapHealthChecks();
630
+ logEvent(
631
+ "restart",
632
+ "exit_event",
633
+ "ok",
634
+ {
635
+ duplicateLaunch: true,
636
+ code: code ?? null,
637
+ signal: signal ?? null,
638
+ stderrTail,
639
+ },
640
+ correlationId,
641
+ );
642
+ return;
643
+ }
608
644
 
609
645
  state.lifecycle = "crashed";
610
646
  state.health = "unhealthy";
647
+ state.uptimeStartedAt = null;
611
648
  state.crashRecoveryActive = true;
612
649
  state.crashTimestamps.push(Date.now());
613
650
  trimCrashWindow();
@@ -677,6 +714,7 @@ const createWatchdog = ({
677
714
  clearDegradedHealthCheckTimer();
678
715
  state.lifecycle = "restarting";
679
716
  state.health = "unknown";
717
+ state.uptimeStartedAt = null;
680
718
  state.startupConsecutiveHealthFailures = 0;
681
719
  state.crashRecoveryActive = false;
682
720
  markExpectedRestartWindow();
@@ -698,7 +736,6 @@ const createWatchdog = ({
698
736
  state.lifecycle = "running";
699
737
  state.health = "unknown";
700
738
  state.startupConsecutiveHealthFailures = 0;
701
- state.uptimeStartedAt = Date.now();
702
739
  state.gatewayStartedAt = Date.now();
703
740
  startBootstrapHealthChecks();
704
741
  };
@@ -714,6 +751,7 @@ const createWatchdog = ({
714
751
  healthTimer = null;
715
752
  }
716
753
  state.lifecycle = "stopped";
754
+ state.uptimeStartedAt = null;
717
755
  state.startupConsecutiveHealthFailures = 0;
718
756
  };
719
757
 
@@ -99,11 +99,16 @@ const normalizeDestination = (destination = null) => {
99
99
  if (!destination || typeof destination !== "object") return null;
100
100
  const channel = String(destination?.channel || "").trim();
101
101
  const to = String(destination?.to || "").trim();
102
+ const agentId = String(destination?.agentId || "").trim();
102
103
  if (!channel && !to) return null;
103
104
  if (!channel || !to) {
104
105
  throw new Error("destination.channel and destination.to are required");
105
106
  }
106
- return { channel, to };
107
+ return {
108
+ channel,
109
+ to,
110
+ ...(agentId ? { agentId } : {}),
111
+ };
107
112
  };
108
113
 
109
114
  const resolveTransformPathFromMapping = (name, mapping) => {
@@ -137,8 +142,7 @@ const normalizeMappingTransformModules = (mappings) => {
137
142
  return changed;
138
143
  };
139
144
 
140
- const buildDefaultTransformSource = (name, destination = null) => {
141
- const normalizedDestination = normalizeDestination(destination);
145
+ const buildDefaultTransformSource = (name) => {
142
146
  return [
143
147
  "export default async function transform(payload, context) {",
144
148
  " const data = payload.payload || payload;",
@@ -146,12 +150,6 @@ const buildDefaultTransformSource = (name, destination = null) => {
146
150
  " message: data.message,",
147
151
  ` name: data.name || "${name}",`,
148
152
  ' wakeMode: data.wakeMode || "now",',
149
- ...(normalizedDestination
150
- ? [
151
- ` channel: ${JSON.stringify(normalizedDestination.channel)},`,
152
- ` to: ${JSON.stringify(normalizedDestination.to)},`,
153
- ]
154
- : []),
155
153
  " };",
156
154
  "}",
157
155
  "",
@@ -164,6 +162,7 @@ const ensureWebhookTransform = ({
164
162
  name,
165
163
  source = "",
166
164
  destination = null,
165
+ forceWrite = false,
167
166
  }) => {
168
167
  const webhookName = validateWebhookName(name);
169
168
  const transformAbsolutePath = getTransformAbsolutePath(
@@ -171,14 +170,14 @@ const ensureWebhookTransform = ({
171
170
  webhookName,
172
171
  );
173
172
  fs.mkdirSync(path.dirname(transformAbsolutePath), { recursive: true });
174
- if (fs.existsSync(transformAbsolutePath)) {
173
+ if (fs.existsSync(transformAbsolutePath) && !forceWrite) {
175
174
  return { changed: false, path: transformAbsolutePath };
176
175
  }
177
176
  fs.writeFileSync(
178
177
  transformAbsolutePath,
179
178
  String(source || "").trim()
180
179
  ? `${String(source).replace(/\s+$/, "")}\n`
181
- : buildDefaultTransformSource(webhookName, destination),
180
+ : buildDefaultTransformSource(webhookName),
182
181
  );
183
182
  return { changed: true, path: transformAbsolutePath };
184
183
  };
@@ -235,6 +234,27 @@ const ensureWebhookMapping = ({ cfg, name, mapping = {} }) => {
235
234
  };
236
235
  };
237
236
 
237
+ const resolveDefaultAgentId = (cfg) => {
238
+ const agents = Array.isArray(cfg?.agents?.list) ? cfg.agents.list : [];
239
+ const explicitDefault = agents.find((entry) => !!entry?.default);
240
+ const defaultId = String(explicitDefault?.id || "").trim();
241
+ if (defaultId) return defaultId;
242
+ const firstId = String(agents[0]?.id || "").trim();
243
+ return firstId || "main";
244
+ };
245
+
246
+ const resolveWebhookAgentId = ({ cfg, requestedAgentId = "" }) => {
247
+ const normalizedRequested = String(requestedAgentId || "").trim();
248
+ const agents = Array.isArray(cfg?.agents?.list) ? cfg.agents.list : [];
249
+ if (
250
+ normalizedRequested &&
251
+ agents.some((entry) => String(entry?.id || "").trim() === normalizedRequested)
252
+ ) {
253
+ return normalizedRequested;
254
+ }
255
+ return resolveDefaultAgentId(cfg);
256
+ };
257
+
238
258
  const listManagedWebhooksFromConfig = ({ cfg }) => {
239
259
  const presets = Array.isArray(cfg?.hooks?.presets) ? cfg.hooks.presets : [];
240
260
  return kManagedWebhookConfigs
@@ -289,6 +309,10 @@ const listWebhooks = ({ fs, constants }) => {
289
309
  path: `/hooks/${name}`,
290
310
  transformPath,
291
311
  transformExists: fs.existsSync(transformAbsolutePath),
312
+ deliver: Boolean(mapping?.deliver),
313
+ channel: String(mapping?.channel || "").trim(),
314
+ to: String(mapping?.to || "").trim(),
315
+ agentId: String(mapping?.agentId || "").trim(),
292
316
  managed: Boolean(managed),
293
317
  managedReason: managed?.managedReason || "",
294
318
  };
@@ -332,6 +356,7 @@ const createWebhook = ({
332
356
  mapping = {},
333
357
  transformSource = "",
334
358
  destination = null,
359
+ overwriteTransform = false,
335
360
  }) => {
336
361
  const webhookName = validateWebhookName(name);
337
362
  const normalizedDestination = normalizeDestination(destination);
@@ -346,10 +371,32 @@ const createWebhook = ({
346
371
  if (exists && !upsert) {
347
372
  throw new Error(`Webhook "${webhookName}" already exists`);
348
373
  }
374
+ const agentId = resolveWebhookAgentId({
375
+ cfg,
376
+ requestedAgentId:
377
+ String(mapping?.agentId || "").trim() ||
378
+ String(normalizedDestination?.agentId || "").trim(),
379
+ });
380
+ const resolvedMapping = {
381
+ ...mapping,
382
+ deliver: true,
383
+ channel:
384
+ String(mapping?.channel || "").trim() ||
385
+ String(normalizedDestination?.channel || "").trim() ||
386
+ "last",
387
+ ...(String(mapping?.to || "").trim() || String(normalizedDestination?.to || "").trim()
388
+ ? {
389
+ to:
390
+ String(mapping?.to || "").trim() ||
391
+ String(normalizedDestination?.to || "").trim(),
392
+ }
393
+ : {}),
394
+ agentId,
395
+ };
349
396
  const ensuredMapping = ensureWebhookMapping({
350
397
  cfg,
351
398
  name: webhookName,
352
- mapping,
399
+ mapping: resolvedMapping,
353
400
  });
354
401
  const ensuredTransform = ensureWebhookTransform({
355
402
  fs,
@@ -357,6 +404,7 @@ const createWebhook = ({
357
404
  name: webhookName,
358
405
  source: transformSource,
359
406
  destination: normalizedDestination,
407
+ forceWrite: overwriteTransform,
360
408
  });
361
409
  if (ensuredMapping.changed || ensuredTransform.changed || !exists) {
362
410
  writeConfig({ fs, configPath, cfg });
package/lib/server.js CHANGED
@@ -72,6 +72,7 @@ const {
72
72
  isGatewayRunning,
73
73
  startGateway,
74
74
  restartGateway: restartGatewayWithReload,
75
+ restartGatewayLight: restartGatewayLightWithReload,
75
76
  attachGatewaySignalHandlers,
76
77
  ensureGatewayProxyConfig,
77
78
  syncChannelConfig,
@@ -90,7 +91,6 @@ const {
90
91
  } = require("./server/restart-required-state");
91
92
  const {
92
93
  ensureOpenclawRuntimeArtifacts,
93
- installControlUiSkill,
94
94
  resolveSetupUiUrl,
95
95
  syncBootstrapPromptFiles,
96
96
  } = require("./server/onboarding/workspace");
@@ -106,6 +106,8 @@ const { createDiscordApi } = require("./server/discord-api");
106
106
  const { createWatchdogNotifier } = require("./server/watchdog-notify");
107
107
  const { createWatchdog } = require("./server/watchdog");
108
108
  const { createDoctorService } = require("./server/doctor/service");
109
+ const { createAgentsService } = require("./server/agents/service");
110
+ const { createOperationEventsService } = require("./server/operation-events");
109
111
  const { runOnboardedBootSequence } = require("./server/startup");
110
112
 
111
113
  const { registerAuthRoutes } = require("./server/routes/auth");
@@ -124,6 +126,7 @@ const { registerWatchdogRoutes } = require("./server/routes/watchdog");
124
126
  const { registerUsageRoutes } = require("./server/routes/usage");
125
127
  const { registerGmailRoutes } = require("./server/routes/gmail");
126
128
  const { registerDoctorRoutes } = require("./server/routes/doctor");
129
+ const { registerAgentRoutes } = require("./server/routes/agents");
127
130
 
128
131
  const { PORT, kTrustProxyHops, SETUP_API_PREFIXES } = constants;
129
132
 
@@ -154,8 +157,17 @@ proxy.on("error", (err, req, res) => {
154
157
  });
155
158
 
156
159
  const authProfiles = createAuthProfiles();
157
- const loginThrottle = { ...createLoginThrottle(), getClientKey };
158
160
  const { shellCmd, clawCmd, gogCmd } = createCommands({ gatewayEnv });
161
+ const agentsService = createAgentsService({
162
+ fs,
163
+ OPENCLAW_DIR: constants.OPENCLAW_DIR,
164
+ readEnvFile,
165
+ writeEnvFile,
166
+ reloadEnv,
167
+ restartGateway: () => restartGatewayWithReload(reloadEnv),
168
+ clawCmd,
169
+ });
170
+ const loginThrottle = { ...createLoginThrottle(), getClientKey };
159
171
  const resolveSetupUrl = () =>
160
172
  resolveSetupUiUrl(
161
173
  process.env.ALPHACLAW_SETUP_URL ||
@@ -171,6 +183,7 @@ const openclawVersionService = createOpenclawVersionService({
171
183
  });
172
184
  const alphaclawVersionService = createAlphaclawVersionService();
173
185
  const restartRequiredState = createRestartRequiredState({ isGatewayRunning });
186
+ const operationEvents = createOperationEventsService();
174
187
 
175
188
  const { requireAuth, isAuthorizedRequest } = registerAuthRoutes({
176
189
  app,
@@ -328,11 +341,6 @@ const doSyncPromptFiles = () => {
328
341
  workspaceDir: constants.WORKSPACE_DIR,
329
342
  baseUrl: setupUiUrl,
330
343
  });
331
- installControlUiSkill({
332
- fs,
333
- openclawDir: constants.OPENCLAW_DIR,
334
- baseUrl: setupUiUrl,
335
- });
336
344
  installGogCliSkill({ fs, openclawDir: constants.OPENCLAW_DIR });
337
345
  };
338
346
  registerTelegramRoutes({
@@ -375,6 +383,12 @@ registerDoctorRoutes({
375
383
  requireAuth,
376
384
  doctorService,
377
385
  });
386
+ registerAgentRoutes({
387
+ app,
388
+ agentsService,
389
+ restartRequiredState,
390
+ operationEvents,
391
+ });
378
392
  registerProxyRoutes({
379
393
  app,
380
394
  proxy,
@@ -21,14 +21,15 @@ Before diving into implementation, share your plan when the work is **significan
21
21
 
22
22
  If any of these apply, outline your approach first — what you intend to do, in what order, and any trade-offs you see — then **wait for the user's sign-off** before proceeding. For straightforward, low-risk tasks, just get it done.
23
23
 
24
- ### Show Your Work (IMPORTANT)
24
+ ### Save and Show Your Work (IMPORTANT)
25
25
 
26
- Mandatory: Anytime you add, edit, or remove files/resources, end your message with a **Changes committed** summary.
26
+ Your `.openclaw` directory is version-controlled and this is how work survives container restarts.
27
27
 
28
- Use workspace-relative paths only for local files (no absolute paths). Include all internal resources (files, config, cron jobs, skills) and external resources (third-party pages, databases, integrations) that were created, modified, or removed.
28
+ Anytime you add, edit, or remove workspace files, openclaw.json, cron.json, skills, or external resources (third-party pages, databases, integrations), **commit and push your changes to git**. Never force push; always pull first if there might be remote changes.
29
+
30
+ Whenever you do this, end your message with a **Changes committed** summary. Use workspace-relative paths for local files.
29
31
 
30
32
  ```
31
- Changes committed ([abc1234](commit url)): <-- linked commit hash
33
+ Changes committed ([abc1234](commit url)): <-- linked abbreviated hash, no backticks
32
34
  • path/or/resource (new|edit|delete) — brief description
33
35
  ```
34
-
@@ -23,14 +23,7 @@ Changes to env vars are made through the **Envars** tab (`{{SETUP_UI_URL}}#envar
23
23
 
24
24
  ### Google Workspace
25
25
 
26
- Google Workspace is connected via the **General** tab (`{{SETUP_UI_URL}}#general`). The user provides OAuth client credentials from Google Cloud Console, then authorizes access to the services they need (Gmail, Calendar, Drive, Sheets, Docs, Tasks, Contacts, Meet).
27
-
28
- ## Git Discipline
29
-
30
- **Commit and push after every set of changes.** Your entire .openclaw directory (config, cron, workspace) is version controlled. This is how your work survives container restarts.
31
-
32
- Never force push. Always pull before pushing if there might be remote changes.
33
- After pushing, include a link to the commit using the abbreviated hash: [abc1234](https://github.com/owner/repo/commit/abc1234) format. No backticks.
26
+ Google Workspace is connected via the **General** tab (`{{SETUP_UI_URL}}#general`). The user provides OAuth client credentials from Google Cloud Console, then authorizes access to the services they need (Gmail, Calendar, Drive, Sheets, Docs, Tasks, Contacts, Meet). Connected accounts and `gog` CLI usage are covered by the gog-cli skill.
34
27
 
35
28
  ## Telegram Formatting
36
29
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrysb/alphaclaw",
3
- "version": "0.5.6",
3
+ "version": "0.6.0-beta.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -1,62 +0,0 @@
1
- ---
2
- name: alphaclaw
3
- description: Know when and how to direct the user to the AlphaClaw UI for configuration tasks.
4
- ---
5
-
6
- # AlphaClaw UI
7
-
8
- There is a web-based Setup UI at `{{BASE_URL}}`. The **user** manages runtime configuration through it. You should NOT call these API endpoints yourself or write config files directly — instead, tell the user what they need to do and where to do it.
9
-
10
- ## When to direct the user to the UI
11
-
12
- ### Adding or changing environment variables
13
-
14
- When the user needs to add a new API key, token, or any env var:
15
-
16
- > You can add that in your Setup UI → **Envars** tab: {{BASE_URL}}#envars
17
-
18
- ### Connecting a new channel (Telegram, Discord)
19
-
20
- > Add your bot token in the Setup UI → **Envars** tab ({{BASE_URL}}#envars), then approve the pairing request in the **General** tab ({{BASE_URL}}#general).
21
-
22
- ### Approving or rejecting pairings
23
-
24
- When a user asks about pairing their Telegram or Discord account:
25
-
26
- > Open the Setup UI → **General** tab ({{BASE_URL}}#general). Pending pairing requests appear automatically — click **Approve** or **Reject**.
27
-
28
- ### Connecting OpenAI Codex OAuth
29
-
30
- > Connect or reconnect Codex OAuth from the Setup UI → **Providers** tab ({{BASE_URL}}#providers). Click **Connect Codex OAuth** and follow the popup flow.
31
-
32
- ### Connecting Google Workspace
33
-
34
- > Set up Google Workspace from the Setup UI → **General** tab ({{BASE_URL}}#general, Google section). You'll need your OAuth client credentials from Google Cloud Console.
35
-
36
- Supported Google services (user selects which to enable during OAuth):
37
-
38
- | Service | Read | Write | Google API |
39
- | -------- | --------------- | ---------------- | ------------------------------ |
40
- | Gmail | `gmail:read` | `gmail:write` | `gmail.googleapis.com` |
41
- | Calendar | `calendar:read` | `calendar:write` | `calendar-json.googleapis.com` |
42
- | Drive | `drive:read` | `drive:write` | `drive.googleapis.com` |
43
- | Sheets | `sheets:read` | `sheets:write` | `sheets.googleapis.com` |
44
- | Docs | `docs:read` | `docs:write` | `docs.googleapis.com` |
45
- | Tasks | `tasks:read` | `tasks:write` | `tasks.googleapis.com` |
46
- | Contacts | `contacts:read` | `contacts:write` | `people.googleapis.com` |
47
- | Meet | `meet:read` | `meet:write` | `meet.googleapis.com` |
48
-
49
- Default enabled: Gmail (read), Calendar (read+write), Drive (read), Sheets (read), Docs (read).
50
-
51
- The `gog` CLI is available to verify Google API access:
52
-
53
- ```bash
54
- gog auth list --plain
55
- gog gmail labels list --account user@gmail.com
56
- gog calendar calendars --account user@gmail.com
57
- gog drive ls --account user@gmail.com
58
- gog sheets metadata SPREADSHEET_ID --account user@gmail.com
59
- gog contacts list --account user@gmail.com
60
- ```
61
-
62
- Config lives at `/data/.openclaw/gogcli/`.