Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ App docs:
- `/app/L0/_all/mod/_core/agent/AGENTS.md`
- `/app/L0/_all/mod/_core/agent-chat/AGENTS.md`
- `/app/L0/_all/mod/_core/agent_prompt/AGENTS.md`
- `/app/L0/_all/mod/_core/anthropic_oauth/AGENTS.md`
- `/app/L0/_all/mod/_core/dashboard/AGENTS.md`
- `/app/L0/_all/mod/_core/dashboard_welcome/AGENTS.md`
- `/app/L0/_all/mod/_core/documentation/AGENTS.md`
Expand Down Expand Up @@ -197,7 +198,7 @@ Project concepts:
- when `CUSTOMWARE_GIT_HISTORY` is enabled, writable `L1/<group>/` and `L2/<user>/` roots are treated as optional per-owner local Git repositories with adaptive-debounced server-side commits and rollback APIs
- `USER_FOLDER_SIZE_LIMIT_BYTES` optionally caps each `L2/<user>/` folder on disk; app-file mutations are checked against a cached per-user size total and only size-reducing mutations are allowed once a user folder is already over the limit
- runtime file discovery is backed by sharded `file_index` state; startup indexes `L0`, `L1`, and layer roots, auth first touch loads only the target user's auth files, and full `L2/<user>` shards are loaded only on demand for file/module access or active mutations rather than preloaded just because their folders exist on disk
- runtime parameters are defined in `commands/params.yaml`; `node space serve` resolves them in this order: launch arguments, stored `.env` params written by `node space set`, then process environment variables, then schema defaults; `node space supervise` accepts the same runtime parameters, owns the public `HOST` and `PORT`, requires `CUSTOMWARE_PATH`, enables source auto-update by default, and passes the remaining resolved params to private `space serve` children; `WORKERS` controls clustered HTTP worker count for `serve` and `supervise`; `CUSTOMWARE_PATH` is the parent directory for writable backend `L1/` and `L2/` storage when configured and also hosts backend-owned cloud-share archives under `share/spaces/` when that feature is enabled; `CUSTOMWARE_WATCHDOG` defaults to `true` and controls live backend customware watching, config watching, and the periodic reconcile backstop without disabling L0/L1 startup indexing, on-demand L2 loading, or explicit clustered mutation sync; `GIT_BACKEND` defaults to `auto` and may force `native` or `isomorphic` for server-owned Git flows such as local history and Git-backed module operations; `LOGIN_ALLOWED` gates the password-login endpoints and login-shell form, `CLOUD_SHARE_ALLOWED` gates hosted cloud-share uploads, `CLOUD_SHARE_URL` tells browser clients which hosted share receiver to use, and page shells receive only `frontend_exposed` values as injected meta tags
- runtime parameters are defined in `commands/params.yaml`; `node space serve` resolves them in this order: launch arguments, stored `.env` params written by `node space set`, then process environment variables, then schema defaults; `node space supervise` accepts the same runtime parameters, owns the public `HOST` and `PORT`, requires `CUSTOMWARE_PATH`, enables source auto-update by default, and passes the remaining resolved params to private `space serve` children; `WORKERS` controls clustered HTTP worker count for `serve` and `supervise`; `CUSTOMWARE_PATH` is the parent directory for writable backend `L1/` and `L2/` storage when configured and also hosts backend-owned cloud-share archives under `share/spaces/` when that feature is enabled; `CUSTOMWARE_WATCHDOG` defaults to `true` and controls live backend customware watching, config watching, and the periodic reconcile backstop without disabling L0/L1 startup indexing, on-demand L2 loading, or explicit clustered mutation sync; `GIT_BACKEND` defaults to `auto` and may force `native` or `isomorphic` for server-owned Git flows such as local history and Git-backed module operations; `LOGIN_ALLOWED` gates the password-login endpoints and login-shell form, `CLOUD_SHARE_ALLOWED` gates hosted cloud-share uploads, `CLOUD_SHARE_URL` tells browser clients which hosted share receiver to use, `ANTHROPIC_OAUTH_ALLOWED` gates the Claude subscription OAuth provider for the agent surfaces and `ANTHROPIC_OAUTH_CLIENT_ID`, `ANTHROPIC_OAUTH_AUTHORIZE_URL`, `ANTHROPIC_OAUTH_TOKEN_URL`, `ANTHROPIC_OAUTH_REDIRECT_URI`, `ANTHROPIC_OAUTH_FLOW_MODE`, and `ANTHROPIC_API_BASE_URL` configure that flow with sane public defaults that work without a per-deployment Anthropic OAuth registration; `ANTHROPIC_OAUTH_FLOW_MODE` defaults to `auto` and selects a button-only redirect flow on `localhost` hosts and the manual code-paste flow elsewhere, and page shells receive only `frontend_exposed` values as injected meta tags
- app file APIs use logical app-rooted paths such as `L2/alice/user.yaml` or `/app/L2/alice/user.yaml`, and supported endpoints may also accept `~` or `~/...` for the authenticated user's `L2/<username>/...`; those logical paths do not change when `CUSTOMWARE_PATH` relocates the writable backend roots
- non-`/api` and non-`/mod` browser entry routes are served from `server/pages/`; `/login` and `/enter` are public and the protected page shells live behind the router-side session gate
- detailed browser-runtime rules live in `/app/AGENTS.md`
Expand Down
2 changes: 2 additions & 0 deletions app/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Current module-local docs in the app tree:
- `app/L0/_all/mod/_core/onscreen_agent/AGENTS.md`
- `app/L0/_all/mod/_core/onscreen_menu/AGENTS.md`
- `app/L0/_all/mod/_core/open_router/AGENTS.md`
- `app/L0/_all/mod/_core/anthropic_oauth/AGENTS.md`
- `app/L1/_all/mod/metrics/posthog/AGENTS.md`
- `app/L0/_admin/mod/_core/overlay_agent/AGENTS.md`

Expand Down Expand Up @@ -133,6 +134,7 @@ Current major first-party modules under `app/L0/_all/mod/_core/`:
- `onscreen_agent/`: floating routed overlay agent and the first-party user-facing chat runtime
- `onscreen_menu/`: reserved routed shell header bar, Home shortcut to the empty default route, left and right shell-control seams, and `_core/onscreen_menu/items` dropdown action seam
- `open_router/`: headless OpenRouter request-policy module that extends the admin and onscreen API transport seams instead of hardcoding provider-specific headers into the chat runtimes
- `anthropic_oauth/`: optional Claude subscription LLM provider; small headless helper plus a reusable connect block embedded under a third tab in the admin and onscreen settings dialogs, plus the per-surface request hooks that redirect API-mode requests to the authenticated `/api/anthropic_subscription_completions` endpoint when the user opted into subscription mode
- `web_browsing/`: browser-surface module that contributes a Browser dropdown action, mounts draggable, minimizable, resizable popup browser windows, and defines placement-generic `<x-browser>` elements that can also live inside widgets or other screen DOM; the same element uses an iframe fallback in browser sessions and a DOM-backed desktop `<webview>` in the packaged host, supports optional `controls="true"` address-bar chrome, registers every surface under a unique `browser-N` id exposed through the numeric-id `space.browser` runtime, includes in-app interception of `_blank` or `window.open(...)` requests back into new modals, persists only popup-window geometry across reloads, tracks direct surface focus for prompt-time browser content, and gates app-side browser diagnostics through a shared browser log level that defaults to `error`
- `skillset/`: first-party shared skill packs plus browser helper scripts and shared browser-side skill discovery helpers used by the onscreen and admin agents
- `webllm/`: unlisted routed browser-only WebLLM test surface with a module-local worker, vendored browser runtime, compact searchable prebuilt model loading, expert-only compiled custom model loading, and simple throughput reporting
Expand Down
6 changes: 4 additions & 2 deletions app/L0/_all/mod/_core/admin/views/agent/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Current persistence paths:

Current stored config fields are written in YAML as:

- `llm_provider`
- `llm_provider` (currently `api`, `subscription`, or `local`)
- `local_provider`
- `api_endpoint`
- `api_key`
Expand Down Expand Up @@ -76,7 +76,9 @@ Prompt rules:

Current behavior:

- the LLM settings modal keeps one provider switch at the top with tabs named `API` and `Local`, and shows either the API settings fields or one `Local` section
- the LLM settings modal keeps one provider switch at the top with three tabs named `API key`, `Claude subscription`, and `Local`, and shows the API settings fields, the subscription connect block, or the `Local` section based on the active tab
- the `Claude subscription` tab is owned by `_core/anthropic_oauth/`; it mounts `connect-block.html` through `<x-component>` and shows a Claude model name input alongside it
- when the active provider is `subscription`, `api.js` validation skips the `apiEndpoint` and `apiKey` checks and the `_core/anthropic_oauth/` request hook redirects the prepared fetch URL to the authenticated `/api/anthropic_subscription_completions` endpoint while stripping any `Authorization` header so the browser never sees the OAuth bearer token
- the `Local` section only supports the Hugging Face browser runtime
- the toolbar LLM settings button summarizes the current selection with the configured model name only; it does not prepend provider labels such as `API`, `Local`, or `Hugging Face`
- the local section mounts the standalone Hugging Face config sidebar component through `<x-component>`, so the admin modal and the routed testing harness share the same component file instead of maintaining duplicated local-provider markup
Expand Down
15 changes: 10 additions & 5 deletions app/L0/_all/mod/_core/admin/views/agent/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -457,12 +457,17 @@ export const prepareAdminAgentApiRequest = globalThis.space.extend(
);

async function streamAdminAgentApiCompletion({ promptContext, settings, systemPrompt, messages, onDelta, signal }) {
if (!settings.apiEndpoint.trim()) {
throw new Error("Set an API endpoint before sending a message.");
}
const provider = config.normalizeAdminChatLlmProvider(settings?.provider);
const isSubscription = provider === config.ADMIN_CHAT_LLM_PROVIDER.SUBSCRIPTION;

if (!settings.apiKey.trim()) {
throw new Error("Set an API key before sending a message.");
if (!isSubscription) {
if (!settings.apiEndpoint.trim()) {
throw new Error("Set an API endpoint before sending a message.");
}

if (!settings.apiKey.trim()) {
throw new Error("Set an API key before sending a message.");
}
}

if (!settings.model.trim()) {
Expand Down
13 changes: 9 additions & 4 deletions app/L0/_all/mod/_core/admin/views/agent/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ export const ADMIN_CHAT_HISTORY_PATH = "~/hist/admin-chat.json";
export const DEFAULT_ADMIN_CHAT_MAX_TOKENS = 120_000;
export const ADMIN_CHAT_LLM_PROVIDER = {
API: "api",
LOCAL: "local"
LOCAL: "local",
SUBSCRIPTION: "subscription"
};

export const ADMIN_CHAT_LOCAL_PROVIDER = {
Expand All @@ -26,9 +27,13 @@ export const DEFAULT_ADMIN_CHAT_SETTINGS = {
};

export function normalizeAdminChatLlmProvider(value) {
return value === ADMIN_CHAT_LLM_PROVIDER.LOCAL
? ADMIN_CHAT_LLM_PROVIDER.LOCAL
: ADMIN_CHAT_LLM_PROVIDER.API;
if (value === ADMIN_CHAT_LLM_PROVIDER.LOCAL) {
return ADMIN_CHAT_LLM_PROVIDER.LOCAL;
}
if (value === ADMIN_CHAT_LLM_PROVIDER.SUBSCRIPTION) {
return ADMIN_CHAT_LLM_PROVIDER.SUBSCRIPTION;
}
return ADMIN_CHAT_LLM_PROVIDER.API;
}

export function normalizeAdminChatLocalProvider(value) {
Expand Down
31 changes: 29 additions & 2 deletions app/L0/_all/mod/_core/admin/views/agent/panel.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
<link rel="stylesheet" href="/mod/_core/visual/index.css" />
<link rel="stylesheet" href="/mod/_core/admin/views/agent/agent.css" />
<link rel="stylesheet" href="/mod/_core/huggingface/huggingface.css" />
<link rel="stylesheet" href="/mod/_core/anthropic_oauth/anthropic-oauth.css" />
<script type="module" src="/mod/_core/admin/views/agent/store.js"></script>
<script type="module" src="/mod/_core/anthropic_oauth/connect-block.js"></script>
</head>
<body>
<div x-data class="admin-agent-root agent-thread-surface">
Expand Down Expand Up @@ -186,7 +188,15 @@ <h2>Provider and model configuration</h2>
:class="{ 'is-active': $store.adminAgent.isSettingsDraftUsingApiProvider }"
@click="$store.adminAgent.setSettingsProvider('api')"
>
<span>API</span>
<span>API key</span>
</button>
<button
type="button"
class="secondary-button dialog-segmented-button"
:class="{ 'is-active': $store.adminAgent.isSettingsDraftUsingSubscriptionProvider }"
@click="$store.adminAgent.setSettingsProvider('subscription')"
>
<span>Claude subscription</span>
</button>
<button
type="button"
Expand All @@ -211,6 +221,23 @@ <h2>Provider and model configuration</h2>
<input type="password" x-model="$store.adminAgent.settingsDraft.apiKey" />
</label>
</div>
<div x-show="$store.adminAgent.isSettingsDraftUsingSubscriptionProvider" x-cloak class="admin-agent-subscription-settings">
<label class="field">
<span>Claude Model</span>
<select
:value="$store.adminAgent.subscriptionModelChoice"
@change="$store.adminAgent.setSubscriptionModelChoice($event.target.value)"
>
<template x-for="entry in $store.adminAgent.subscriptionCuratedModels" :key="entry.value">
<option :value="entry.value" x-text="entry.label"></option>
</template>
</select>
<p class="field-note">
Pick a Claude model. Anthropic resolves the alias to the latest dated release, so newer models work without a Space Agent update. Effort, thinking, and other Claude defaults are handled by the subscription.
</p>
</label>
<x-component path="/mod/_core/anthropic_oauth/connect-block.html" mode="admin"></x-component>
</div>
<div x-show="$store.adminAgent.isSettingsDraftUsingLocalProvider" x-cloak class="admin-agent-local-settings">
<div class="admin-agent-local-provider-panel">
<x-component path="/mod/_core/huggingface/config-sidebar.html" mode="admin"></x-component>
Expand Down Expand Up @@ -284,7 +311,7 @@ <h2>Provider and model configuration</h2>
System, history, and transient rebalance to 100% of <code>max tokens</code>. Single history message is a percentage of the history budget.
</p>
</div>
<label class="field">
<label class="field" x-show="!$store.adminAgent.isSettingsDraftUsingSubscriptionProvider" x-cloak>
<span>Params</span>
<textarea
rows="6"
Expand Down
54 changes: 54 additions & 0 deletions app/L0/_all/mod/_core/admin/views/agent/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import {
installPromptItemAccess,
rebalancePromptBudgetRatios
} from "/mod/_core/agent_prompt/prompt-items.js";
import {
ANTHROPIC_SUBSCRIPTION_CURATED_MODELS,
ANTHROPIC_SUBSCRIPTION_DEFAULT_MODEL,
isAnthropicSubscriptionCuratedModel,
normalizeSubscriptionModelId
} from "/mod/_core/anthropic_oauth/request.js";
import * as execution from "/mod/_core/admin/views/agent/execution.js";
import * as llmParams from "/mod/_core/admin/views/agent/llm-params.js";
import * as prompt from "/mod/_core/admin/views/agent/prompt.js";
Expand Down Expand Up @@ -484,6 +490,32 @@ const model = {
return config.normalizeAdminChatLlmProvider(this.settingsDraft.provider) === config.ADMIN_CHAT_LLM_PROVIDER.LOCAL;
},

get isSettingsDraftUsingSubscriptionProvider() {
return (
config.normalizeAdminChatLlmProvider(this.settingsDraft.provider) ===
config.ADMIN_CHAT_LLM_PROVIDER.SUBSCRIPTION
);
},

get isUsingSubscriptionProvider() {
return (
config.normalizeAdminChatLlmProvider(this.settings.provider) ===
config.ADMIN_CHAT_LLM_PROVIDER.SUBSCRIPTION
);
},

get subscriptionCuratedModels() {
return ANTHROPIC_SUBSCRIPTION_CURATED_MODELS.map((entry) => ({ ...entry }));
},

get subscriptionModelChoice() {
const normalized = normalizeSubscriptionModelId(this.settingsDraft.model);
if (!normalized || !isAnthropicSubscriptionCuratedModel(normalized)) {
return ANTHROPIC_SUBSCRIPTION_DEFAULT_MODEL;
}
return normalized;
},

get huggingfaceSavedModels() {
return Array.isArray(this.huggingface.savedModels) ? this.huggingface.savedModels : [];
},
Expand Down Expand Up @@ -1667,6 +1699,28 @@ const model = {
this.reportError("warming the local-provider settings draft", error);
});
}

if (this.isSettingsDraftUsingSubscriptionProvider) {
const currentModel = String(this.settingsDraft.model || "").trim().toLowerCase();
const looksClaude = currentModel.includes("claude") || currentModel.startsWith("anthropic/");
if (!looksClaude) {
this.settingsDraft = {
...this.settingsDraft,
model: ANTHROPIC_SUBSCRIPTION_DEFAULT_MODEL
};
}
}
},

setSubscriptionModelChoice(value) {
const choice = String(value || "").trim();
if (!choice) {
return;
}
this.settingsDraft = {
...this.settingsDraft,
model: choice
};
},

setSettingsPromptBudgetRatio(key, value) {
Expand Down
Loading