@meetopenbot/openbot 1.1.1 → 1.2.1

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.
@@ -7,6 +7,7 @@ import { startWorkTools } from "./tools/start-work.js";
7
7
  import { threadStatusTools } from "./tools/thread-status.js";
8
8
  import { threadTitleTools } from "./tools/thread-title.js";
9
9
  import { wrapToolsWithApproval } from "./tools/approval.js";
10
+ export const DEFAULT_APPROVAL_ACTIONS = ["update_channel_spec"];
10
11
  /** Always gated: installing an agent changes the workspace. */
11
12
  const ALWAYS_APPROVE = ["install_agent"];
12
13
  /**
@@ -25,6 +26,6 @@ export function buildOpenbotTools(args) {
25
26
  ...threadTitleTools,
26
27
  ...args.context.tools,
27
28
  };
28
- const approvalConfig = args.context.config?.approval ?? { actions: [] };
29
- return wrapToolsWithApproval(tools, [...new Set([...ALWAYS_APPROVE, ...(approvalConfig.actions ?? [])])], args.context);
29
+ const approvalConfig = args.context.config?.approval;
30
+ return wrapToolsWithApproval(tools, [...new Set([...ALWAYS_APPROVE, ...(approvalConfig?.actions ?? DEFAULT_APPROVAL_ACTIONS)])], args.context);
30
31
  }
package/dist/runtime.js CHANGED
@@ -3,14 +3,18 @@ import { BYOK_ENV_KEYS, isAuthErrorMessage, isCreditsErrorMessage, fetchModelReg
3
3
  import { eventsToModelMessages } from './history.js';
4
4
  import { addImageParts, eventsHaveImages, pickVisionModel } from './images.js';
5
5
  import { buildContext } from './context.js';
6
- import { OPENBOT_SYSTEM_PROMPT, withUserInstructions } from './system-prompt.js';
6
+ import { OPENBOT_SYSTEM_PROMPT, withGithubGitInstructions, withUserInstructions, } from './system-prompt.js';
7
7
  import { AUTO_MODEL_ID, isAutoModel, isRetryableModelError, listAutoModelCandidates, missingByokApiKeyMessage, } from './auto-model.js';
8
8
  import { reasoningForModel, resolveModel } from './model.js';
9
9
  import { errorFromStreamPart } from './stream-error.js';
10
10
  import { createAgentOutputBuffer, isOutputBoundaryPart } from './output-buffer.js';
11
11
  async function buildSystemPrompt(state, storage, instructions, host) {
12
12
  const context = await buildContext(state, storage, host);
13
- const sections = [withUserInstructions(OPENBOT_SYSTEM_PROMPT, instructions), '', context];
13
+ const sections = [
14
+ withGithubGitInstructions(withUserInstructions(OPENBOT_SYSTEM_PROMPT, instructions)),
15
+ '',
16
+ context,
17
+ ];
14
18
  const threadState = state.threadDetails?.state;
15
19
  if (!threadState?.isSmartNamed) {
16
20
  sections.push('', '## SYSTEM HINT', 'This thread is unnamed. Please use the `set_thread_title` tool to set a concise, descriptive title (e.g., "Project Brainstorming" instead of "project-brainstorm"). Only do this once.');
@@ -49,6 +49,12 @@ export const OPENBOT_SYSTEM_PROMPT = [
49
49
  "- **Hub-and-spoke**: Specialists cannot talk to each other. You pass context from one to the next.",
50
50
  "- **Durable Memory**: Use the `remember` tool to store important facts, preferences, or project details that should persist across sessions.",
51
51
  "",
52
+ "# CLOUD COMPUTER",
53
+ "This runtime is a remote cloud computer. The human is in the browser at openbot.one and cannot open localhost, 127.0.0.1, or any port on this machine.",
54
+ "- Never tell them to visit localhost. They need a public HTTPS URL.",
55
+ "- When you ask a coding agent to run a dev server, preview, or any local process the human should open, tell them to expose it with `cloudflared tunnel --url http://127.0.0.1:<port>` and to add `cloudflared` as a **project devDependency** (not a global install — this image is ephemeral and only workspace files persist).",
56
+ "- Share the `https://*.trycloudflare.com` URL they print. Quick tunnels change on restart — pass the new one through. If they report a localhost URL, ask them to tunnel it.",
57
+ "",
52
58
  "# COMMUNICATION & WRITING STYLE",
53
59
  "- Be concise, professional, proactive, and polite.",
54
60
  "- Confirm receipt quickly. For longer work, say where you are taking it and who you will ask.",
@@ -61,6 +67,27 @@ export const OPENBOT_SYSTEM_PROMPT = [
61
67
  "- If an asked agent fails, try a different agent or tell the human what blocked them.",
62
68
  "- Only ask the human for help after you cannot route or ask anyone who can do the work.",
63
69
  ].join("\n");
70
+ /**
71
+ * Appended when `GITHUB_TOKEN` is in the runtime env (workspace variables are
72
+ * merged in). Git does not read that variable; coding agents need an inline hint.
73
+ */
74
+ export const GITHUB_GIT_INSTRUCTIONS = [
75
+ "# GITHUB GIT",
76
+ "`GITHUB_TOKEN` is set in this runtime's environment. Git does not read it automatically.",
77
+ "When you `ask_agent` a coding agent (Claude Code, Codex, Cursor, OpenCode, Pi) to clone, fetch, pull, or push GitHub, tell them in that prompt:",
78
+ "- `GITHUB_TOKEN` is already in their environment. Never paste the token value into the prompt, chat, or a command you write out.",
79
+ "- Use it inline and keep `$GITHUB_TOKEN` unexpanded, e.g. `git push https://x-access-token:$GITHUB_TOKEN@github.com/<owner>/<repo>.git <branch>`.",
80
+ "- Do not write the token into a git remote (`.git/config`) or print `git remote -v` after embedding it.",
81
+ ].join("\n");
82
+ export function githubTokenIsSet(env = process.env) {
83
+ return Boolean(env.GITHUB_TOKEN?.trim());
84
+ }
85
+ /** Append GitHub git guidance when `GITHUB_TOKEN` is present. */
86
+ export function withGithubGitInstructions(basePrompt, env = process.env) {
87
+ if (!githubTokenIsSet(env))
88
+ return basePrompt;
89
+ return `${basePrompt}\n\n${GITHUB_GIT_INSTRUCTIONS}`;
90
+ }
64
91
  /** Max characters accepted from the OpenBot plugin `instructions` config field. */
65
92
  export const MAX_USER_INSTRUCTIONS_LENGTH = 4000;
66
93
  /** Trim and cap user-provided coordinator instructions. */
@@ -53,6 +53,22 @@ export const storageTools = {
53
53
  const { channelId, name, spec, initialState } = (raw ?? {});
54
54
  const rawChannelId = normalizeChannelId(channelId || '');
55
55
  const widgetId = ctx.toolCallId ?? `create_channel:${rawChannelId || Date.now()}`;
56
+ if (ctx.agentId !== ctx.host.orchestratorAgentId) {
57
+ const error = 'Only OpenBot can create Spaces.';
58
+ await emitCreateChannelWidget(ctx, {
59
+ widgetId,
60
+ channelId: rawChannelId,
61
+ body: error,
62
+ state: 'error',
63
+ });
64
+ return {
65
+ success: false,
66
+ channelId: rawChannelId,
67
+ channelUrl: '',
68
+ error,
69
+ output: error,
70
+ };
71
+ }
56
72
  if (!rawChannelId) {
57
73
  const error = 'channelId is required';
58
74
  await emitCreateChannelWidget(ctx, {
@@ -131,6 +147,77 @@ export const storageTools = {
131
147
  }
132
148
  },
133
149
  },
150
+ update_channel_spec: {
151
+ description: 'Replace an existing Space spec with new markdown only when the human explicitly asked for the update or agreed to it.',
152
+ inputSchema: z.object({
153
+ channelId: z.string().describe('Existing Space id whose spec should be replaced.'),
154
+ spec: z.string().describe('Complete replacement markdown for the Space spec.'),
155
+ }),
156
+ execute: async (raw, ctx) => {
157
+ const { channelId, spec } = (raw ?? {});
158
+ const normalizedChannelId = normalizeChannelId(channelId || '');
159
+ if (ctx.agentId !== ctx.host.orchestratorAgentId) {
160
+ const error = 'Only OpenBot can update Space specs.';
161
+ return {
162
+ success: false,
163
+ channelId: normalizedChannelId,
164
+ error,
165
+ output: error,
166
+ };
167
+ }
168
+ if (!normalizedChannelId) {
169
+ const error = 'channelId is required';
170
+ return { success: false, channelId: '', error, output: error };
171
+ }
172
+ if (isPlaceholderSpaceId(normalizedChannelId)) {
173
+ const error = `Refused to update #${normalizedChannelId}: that looks like a placeholder, not a real Space.`;
174
+ return {
175
+ success: false,
176
+ channelId: normalizedChannelId,
177
+ error,
178
+ output: error,
179
+ };
180
+ }
181
+ if (typeof spec !== 'string') {
182
+ const error = 'spec is required';
183
+ return {
184
+ success: false,
185
+ channelId: normalizedChannelId,
186
+ error,
187
+ output: error,
188
+ };
189
+ }
190
+ try {
191
+ const channels = await ctx.storage.getChannels();
192
+ const exists = channels.some((channel) => channel.id === normalizedChannelId);
193
+ if (!exists) {
194
+ const error = `Space #${normalizedChannelId} does not exist.`;
195
+ return {
196
+ success: false,
197
+ channelId: normalizedChannelId,
198
+ error,
199
+ output: error,
200
+ };
201
+ }
202
+ await ctx.storage.patchChannelSpec({
203
+ channelId: normalizedChannelId,
204
+ spec,
205
+ });
206
+ const output = `Updated the spec for Space #${normalizedChannelId}.`;
207
+ return { success: true, channelId: normalizedChannelId, output };
208
+ }
209
+ catch (error) {
210
+ const message = error instanceof Error ? error.message : 'Unknown error';
211
+ const output = `Failed to update the spec for Space #${normalizedChannelId}: ${message}`;
212
+ return {
213
+ success: false,
214
+ channelId: normalizedChannelId,
215
+ error: message,
216
+ output,
217
+ };
218
+ }
219
+ },
220
+ },
134
221
  };
135
222
  export const tools = storageTools;
136
223
  export const storageToolPlugin = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meetopenbot/openbot",
3
- "version": "1.1.1",
3
+ "version": "1.2.1",
4
4
  "description": "OpenBot coordinator runtime: ask specialists, route work into Spaces, and track todos.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -25,7 +25,7 @@
25
25
  "@ai-sdk/openai": "^4.0.46",
26
26
  "ai": "^7.0.77",
27
27
  "zod": "^4.3.5",
28
- "@meetopenbot/plugin-sdk": "^1.0.5"
28
+ "@meetopenbot/plugin-sdk": "^1.1.2"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/node": "^20.10.1",