Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
e53d3cf
Add Web Console DESIGN docs (EN & CN)
franknobox Mar 17, 2026
6b0c0ef
Merge branch 'alpha-test' into feat/alpha-webchat-20260317
franknobox Mar 17, 2026
64f6798
Reorganize web docs; add Chinese API & STACK
franknobox Mar 17, 2026
a6b42d5
Add local Web Console API and daemon support
franknobox Mar 17, 2026
441b664
Revamp dashboard UI, styles, and locales
franknobox Mar 18, 2026
dba9e49
Add web dev start/stop scripts and logs
franknobox Mar 18, 2026
6e2bdb9
Merge branch 'dev' into feat/alpha-webchat-20260317
franknobox Mar 18, 2026
f0fbe7f
Switch web preview to dev; update UI and docs
franknobox Mar 18, 2026
3bcfb21
Add OpenAI-style provider streaming & web API
franknobox Mar 18, 2026
4ba4840
Merge remote-tracking branch 'upstream/dev' into feat/alpha-webchat-2…
franknobox Mar 18, 2026
5b78679
Show memory window in chat inspector
franknobox Mar 18, 2026
a5418c8
Add temporary Web tool-assist hint and UI toggle
franknobox Mar 18, 2026
7ea4a2e
Merge remote-tracking branch 'upstream/dev' into feat/alpha-webchat-2…
franknobox Mar 19, 2026
2178a5f
Add web onboarding API and UI
franknobox Mar 19, 2026
dc7d300
Extract onboarding handlers; add dashboard CSS
franknobox Mar 19, 2026
3f65e2b
Merge branch 'loongclaw-ai:dev' into feat/alpha-webchat-20260317
franknobox Mar 19, 2026
b5ac4ba
Add onboarding pairing & preferences APIs and local CORS
franknobox Mar 19, 2026
932c360
Include token path/env in auth error messages
franknobox Mar 20, 2026
ffcf984
Accept request_rejected as valid during onboarding
franknobox Mar 20, 2026
da97dd2
Add provider apply endpoint & dashboard flow
franknobox Mar 20, 2026
26d8365
Merge upstream/dev into feat/dev-webchat-20260317
franknobox Mar 20, 2026
ea24dee
Add web dev start/stop scripts
franknobox Mar 20, 2026
7a9aa5e
Dashboard: runtime/config UI and tool runtime info
franknobox Mar 20, 2026
f05c287
Add dashboard debug console and runtime tracing
franknobox Mar 21, 2026
ba8968f
Support same-origin static UI and session auth
franknobox Mar 21, 2026
1e6a325
Move web scripts to scripts/ and adjust paths
franknobox Mar 21, 2026
c06eccb
web: add docs and improve API client errors
franknobox Mar 21, 2026
1ac8e87
Keep-alive routes & API client overhaul
franknobox Mar 21, 2026
2370777
Merge branch 'dev' into feat/dev-webchat-20260317
franknobox Mar 22, 2026
240a9b4
feat(web): add web install/status/remove commands
franknobox Mar 22, 2026
35ff9fa
Format code, reorder imports, and update web docs
franknobox Mar 22, 2026
f4f29bc
Update INSTALL.md
franknobox Mar 22, 2026
197cff1
Refactor session logic; add chat hooks & i18n
franknobox Mar 22, 2026
49ee3d8
Handle session removal and resilient chat streaming
franknobox Mar 22, 2026
8793043
Merge remote-tracking branch 'upstream/dev' into feat/dev-webchat-202…
franknobox Mar 22, 2026
3393374
Remove temp logs and add missing None args in tests
franknobox Mar 22, 2026
d0f352b
Move session helpers into sqlite module
franknobox Mar 22, 2026
605cb66
Update sqlite.rs
franknobox Mar 22, 2026
0aad57c
chore: refresh PR merge ref
franknobox Mar 22, 2026
ccbc955
Merge pull request #450 from franknobox/feat/dev-webchat-20260317
franknobox Mar 22, 2026
4aaf2aa
ACP event sink, same-origin, dashboard form fixes
franknobox Mar 23, 2026
1e9d529
Merge pull request #460 from franknobox/feat/dev-webchat-20260317
franknobox Mar 23, 2026
6459e15
Merge remote-tracking branch 'origin/dev' into web
franknobox Mar 23, 2026
ed32980
Merge pull request #461 from loongclaw-ai/chore/sync-dev-into-web
franknobox Mar 23, 2026
df6ec90
Merge branch 'dev' into web
franknobox Mar 28, 2026
2b7cabc
Merge pull request #684 from loongclaw-ai/chore/sync-dev-into-web-202…
franknobox Mar 29, 2026
e774afd
Merge branch 'origin/dev' into web
franknobox Apr 1, 2026
5a83f26
fix: satisfy strict web branch quality checks
franknobox Apr 1, 2026
007ed62
fix(web): refine onboarding flow and remove tool assist workaround
franknobox Apr 1, 2026
ff60309
fix(web): improve chat rendering and dashboard follow-ups
franknobox Apr 2, 2026
aa2437d
Merge pull request #808 from loongclaw-ai/chore/web-followups-20260402
franknobox Apr 2, 2026
1809e77
refactor(web): split daemon web modules
franknobox Apr 3, 2026
7fb12a0
fix(dashboard): reduce config snapshot stitching
franknobox Apr 3, 2026
eb1ca5b
feat(web): refine delivery readiness across chat, dashboard, onboardi…
franknobox Apr 4, 2026
24ac6f4
Merge pull request #891 from loongclaw-ai/chore/web-delivery-followup…
franknobox Apr 4, 2026
cdd2f07
merge: sync latest dev into web
franknobox Apr 4, 2026
4ddf937
feat(web): expand navigation and refine agent-facing chat/status surf…
franknobox Apr 4, 2026
8161201
Merge pull request #901 from loongclaw-ai/chore/web-abilities-status-…
franknobox Apr 4, 2026
b534eb5
Merge remote-tracking branch 'origin/dev' into web
franknobox Apr 5, 2026
e6d3ba1
feat(web): add abilities workspace and wire initial abilities data
franknobox Apr 5, 2026
c007b8f
feat(web): flesh out abilities personalization and refine chat sessio…
franknobox Apr 5, 2026
6bd074b
Merge remote-tracking branch 'origin/dev' into web
franknobox Apr 6, 2026
3b411e8
feat(web): expand abilities workspace and polish runtime surfaces
franknobox Apr 6, 2026
c156d44
Merge pull request #1004 from loongclaw-ai/chore/web-abilities-runtim…
franknobox Apr 6, 2026
826907c
fix(cli): restore web subcommand buildability
franknobox Apr 7, 2026
1db43a1
Merge pull request #1082 from loongclaw-ai/chore/web-cli-buildability…
franknobox Apr 7, 2026
b6d8c37
fix(unix): restore daemon build after dev merge
franknobox Apr 7, 2026
db5477c
Merge pull request #1084 from loongclaw-ai/chore/web-cli-buildability…
franknobox Apr 7, 2026
0828dd0
Merge remote-tracking branch 'origin/dev' into web
franknobox Apr 10, 2026
764cf6d
Fix web provider setup and onboarding form flow
franknobox Apr 10, 2026
498e265
Sync web docs with runtime and provider updates
franknobox Apr 11, 2026
3a25cd3
Merge pull request #1199 from loongclaw-ai/chore/web-runtime-doc-sync…
franknobox Apr 11, 2026
d024b7b
Merge origin/dev into web
franknobox Apr 11, 2026
7703817
Adapt web chat turns to the updated production coordinator API
franknobox Apr 11, 2026
6e32bd7
Polish web runtime forms and chat state sync
franknobox Apr 11, 2026
05dc641
Refine web memory window input behavior
franknobox Apr 11, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/.docs
/.loongclaw.local.toml
/.env.local
/.tmp-web-*.log
.worktrees/
.idea
.vscode
Expand Down
6 changes: 5 additions & 1 deletion crates/app/src/chat/cli_input.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#[cfg(unix)]
use std::fs::OpenOptions;
#[cfg(unix)]
use std::io::Read;

#[cfg(unix)]
use std::os::unix::fs::OpenOptionsExt;

Expand All @@ -11,6 +12,7 @@ use tokio::io::{self as tokio_io, AsyncBufReadExt, BufReader};

use crate::CliResult;

#[cfg(any(unix, test))]
pub(super) fn extract_cli_input_line_from_buffer(
buffer: &mut Vec<u8>,
) -> CliResult<Option<String>> {
Expand All @@ -26,6 +28,7 @@ pub(super) fn extract_cli_input_line_from_buffer(
Ok(Some(line))
}

#[cfg(any(unix, test))]
pub(super) fn finalize_cli_input_buffer(buffer: &mut Vec<u8>) -> CliResult<Option<String>> {
if buffer.is_empty() {
return Ok(None);
Expand All @@ -39,6 +42,7 @@ pub(super) fn finalize_cli_input_buffer(buffer: &mut Vec<u8>) -> CliResult<Optio
Ok(Some(line))
}

#[cfg(any(unix, test))]
fn normalize_cli_input_line_bytes(mut bytes: Vec<u8>) -> Vec<u8> {
if bytes.last() == Some(&b'\n') {
bytes.pop();
Expand Down
15 changes: 8 additions & 7 deletions crates/app/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,14 @@ pub(crate) use provider::{
#[allow(unused_imports)]
pub use provider::{
ModelCatalogProbeRecovery, PROVIDER_DESCRIPTOR_SCHEMA_VERSION, ProviderAuthScheme,
ProviderConfig, ProviderDescriptorAuth, ProviderDescriptorDocument, ProviderDescriptorFeature,
ProviderDescriptorHeader, ProviderDescriptorRegionEndpoint, ProviderDescriptorRegionVariant,
ProviderDescriptorSchema, ProviderFeatureFamily, ProviderKind, ProviderProfileConfig,
ProviderProfileHealthModeConfig, ProviderProfileStateBackendKind, ProviderProtocolFamily,
ProviderReasoningExtraBodyModeConfig, ProviderToolSchemaModeConfig, ProviderTransportFallback,
ProviderTransportPolicy, ProviderTransportReadiness, ProviderTransportReadinessLevel,
ProviderWireApi, ReasoningEffort, parse_provider_kind_id,
ProviderCatalogEntry, ProviderConfig, ProviderDescriptorAuth, ProviderDescriptorDocument,
ProviderDescriptorFeature, ProviderDescriptorHeader, ProviderDescriptorRegionEndpoint,
ProviderDescriptorRegionVariant, ProviderDescriptorSchema, ProviderFeatureFamily, ProviderKind,
ProviderProfileConfig, ProviderProfileHealthModeConfig, ProviderProfileStateBackendKind,
ProviderProtocolFamily, ProviderReasoningExtraBodyModeConfig, ProviderToolSchemaModeConfig,
ProviderTransportFallback, ProviderTransportPolicy, ProviderTransportReadiness,
ProviderTransportReadinessLevel, ProviderWireApi, ReasoningEffort, parse_provider_kind_id,
provider_catalog_entries,
};
#[cfg(test)]
pub(crate) use runtime::inject_test_config_write_failure;
Expand Down
78 changes: 77 additions & 1 deletion crates/app/src/config/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,21 @@ pub struct ProviderProfile {
pub feature_family: ProviderFeatureFamily,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ProviderCatalogEntry {
pub kind: String,
pub display_name: String,
pub default_base_url: String,
pub default_chat_path: String,
pub default_models_path: Option<String>,
pub auth_scheme: String,
pub protocol_family: String,
pub feature_family: String,
pub is_coding_variant: bool,
pub aliases: Vec<String>,
pub configuration_hint: Option<String>,
}

impl ProviderProfile {
pub fn alternative_auth_configuration_hint(self) -> Option<&'static str> {
let kind = self.kind;
Expand Down Expand Up @@ -2018,7 +2033,7 @@ impl ProviderConfig {
|| contains_template_placeholder(resolved_base_url.as_str())
}

pub fn configuration_hint(&self) -> Option<String> {
pub fn kind_route_mismatch_hint(&self) -> Option<String> {
if self.kind == ProviderKind::Byteplus && self.uses_byteplus_coding_plan_path() {
return Some(
"byteplus uses the standard ModelArk path and should not target `/api/coding` or `/api/coding/v3`; switch to `kind = \"byteplus_coding\"` for the dedicated OpenAI-compatible Coding Plan endpoint"
Expand Down Expand Up @@ -2049,6 +2064,14 @@ impl ProviderConfig {
.to_owned(),
);
}

None
}

pub fn configuration_hint(&self) -> Option<String> {
if let Some(hint) = self.kind_route_mismatch_hint() {
return Some(hint);
}
if let Some(hint) = self.opencode_configuration_hint() {
return Some(hint);
}
Expand Down Expand Up @@ -2569,6 +2592,28 @@ fn is_provider_managed_oauth_access_token_env_name(env_name: &str) -> bool {
})
}

fn default_models_path_for_kind(kind: ProviderKind) -> Option<String> {
let profile = kind.profile();
if let Some(path) = profile.models_path {
return Some(path.to_owned());
}

let provider = ProviderConfig::fresh_for_kind(kind);
let base_url = provider.resolved_base_url();
let models_endpoint = provider.models_endpoint();
models_endpoint
.strip_prefix(base_url.as_str())
.map(str::to_owned)
}

pub fn provider_catalog_entries() -> Vec<ProviderCatalogEntry> {
ProviderKind::all_sorted()
.iter()
.copied()
.map(ProviderKind::catalog_entry)
.collect()
}

fn maybe_normalize_custom_chat_path(kind: ProviderKind, base_url: &str, path: &str) -> String {
let normalized = normalize_api_path(path);
if kind != ProviderKind::Custom {
Expand Down Expand Up @@ -2771,6 +2816,37 @@ impl ProviderKind {
self.profile().feature_family
}

pub const fn is_coding_variant(self) -> bool {
matches!(
self,
ProviderKind::BailianCoding
| ProviderKind::ByteplusCoding
| ProviderKind::KimiCoding
| ProviderKind::VolcengineCoding
)
}

pub fn catalog_entry(self) -> ProviderCatalogEntry {
let profile = self.profile();
ProviderCatalogEntry {
kind: self.as_str().to_owned(),
display_name: self.display_name().to_owned(),
default_base_url: profile.base_url.to_owned(),
default_chat_path: profile.chat_completions_path.to_owned(),
default_models_path: default_models_path_for_kind(self),
auth_scheme: self.auth_scheme().as_str().to_owned(),
protocol_family: self.protocol_family().as_str().to_owned(),
feature_family: self.feature_family().as_str().to_owned(),
is_coding_variant: self.is_coding_variant(),
aliases: profile
.aliases
.iter()
.map(|alias| (*alias).to_owned())
.collect(),
configuration_hint: self.configuration_hint().map(str::to_owned),
}
}

pub fn default_headers(self) -> &'static [(&'static str, &'static str)] {
self.profile().default_headers
}
Expand Down
22 changes: 18 additions & 4 deletions crates/app/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,25 @@ impl KernelContext {
}
}

/// Bootstrap a minimal in-memory kernel suitable for tests.
/// Bootstrap a minimal in-memory kernel with ephemeral audit retention.
///
/// This is a convenience entry point for MVP and development flows where
/// durable audit storage is not required. It registers a default pack manifest
/// with `InvokeTool`, `MemoryRead`, and `MemoryWrite` capabilities, then
/// issues a long-lived token for the given `agent_id`.
///
/// Registers a default pack manifest with the MVP tool, memory, filesystem,
/// and public-web capabilities, then issues a long-lived token for the given
/// `agent_id`.
/// Production-facing runtime entrypoints should prefer
/// [`bootstrap_kernel_context_with_config`] so audit retention follows config.
pub fn bootstrap_kernel_context(agent_id: &str, ttl_s: u64) -> Result<KernelContext, String> {
bootstrap_kernel_context_with_audit_sink(
agent_id,
ttl_s,
Arc::new(InMemoryAuditSink::default()) as Arc<dyn AuditSink>,
&LoongClawConfig::default(),
)
}

/// Bootstrap a minimal in-memory kernel suitable for tests.
///
/// Production-facing runtime entrypoints should prefer
/// `bootstrap_kernel_context_with_config` so audit retention follows config.
Expand Down
32 changes: 32 additions & 0 deletions crates/app/src/conversation/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1380,6 +1380,21 @@ pub trait ConversationRuntime: Send + Sync {
binding: ConversationRuntimeBinding<'_>,
) -> CliResult<ProviderTurn>;

async fn request_turn_with_event_sink(
&self,
config: &LoongClawConfig,
session_id: &str,
turn_id: &str,
messages: &[Value],
tool_view: &ToolView,
event_sink: Option<&dyn crate::acp::AcpTurnEventSink>,
binding: ConversationRuntimeBinding<'_>,
) -> CliResult<ProviderTurn> {
let _ = event_sink;
self.request_turn(config, session_id, turn_id, messages, tool_view, binding)
.await
}

async fn request_turn_streaming(
&self,
config: &LoongClawConfig,
Expand Down Expand Up @@ -1592,13 +1607,30 @@ where
messages: &[Value],
tool_view: &ToolView,
binding: ConversationRuntimeBinding<'_>,
) -> CliResult<ProviderTurn> {
self.request_turn_with_event_sink(
config, session_id, turn_id, messages, tool_view, None, binding,
)
.await
}

async fn request_turn_with_event_sink(
&self,
config: &LoongClawConfig,
session_id: &str,
turn_id: &str,
messages: &[Value],
tool_view: &ToolView,
event_sink: Option<&dyn crate::acp::AcpTurnEventSink>,
binding: ConversationRuntimeBinding<'_>,
) -> CliResult<ProviderTurn> {
provider::request_turn_in_view(
config,
session_id,
turn_id,
messages,
tool_view,
event_sink,
provider_runtime_binding(binding),
)
.await
Expand Down
20 changes: 19 additions & 1 deletion crates/app/src/conversation/turn_coordinator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,7 @@ impl ProviderTurnContinuePhase {
turn_loop_policy: &ProviderTurnLoopPolicy,
turn_loop_state: &mut ProviderTurnLoopState,
remaining_provider_rounds: usize,
acp_event_sink: Option<&dyn AcpTurnEventSink>,
binding: ConversationRuntimeBinding<'_>,
observer: Option<&ConversationTurnObserverHandle>,
) -> ResolvedProviderTurn {
Expand All @@ -804,6 +805,7 @@ impl ProviderTurnContinuePhase {
turn_loop_policy,
turn_loop_state,
remaining_provider_rounds,
acp_event_sink,
binding,
self.ingress.as_ref(),
observer,
Expand Down Expand Up @@ -2024,6 +2026,7 @@ impl ConversationTurnCoordinator {
preparation.turn_id.as_str(),
&preparation.session.messages,
&tool_view,
acp_options.event_sink,
binding,
observer.as_ref(),
)
Expand All @@ -2036,6 +2039,7 @@ impl ConversationTurnCoordinator {
&preparation,
provider_turn_result,
error_mode,
acp_options.event_sink,
binding,
ingress,
observer.as_ref(),
Expand Down Expand Up @@ -2161,6 +2165,7 @@ impl ConversationTurnCoordinator {
&preparation,
Ok(approval_turn),
error_mode,
None,
binding,
None,
observer,
Expand Down Expand Up @@ -2824,6 +2829,7 @@ async fn request_provider_turn_with_observer<R: ConversationRuntime + ?Sized>(
turn_id: &str,
messages: &[Value],
tool_view: &crate::tools::ToolView,
acp_event_sink: Option<&dyn AcpTurnEventSink>,
binding: ConversationRuntimeBinding<'_>,
observer: Option<&ConversationTurnObserverHandle>,
) -> CliResult<ProviderTurn> {
Expand All @@ -2839,7 +2845,15 @@ async fn request_provider_turn_with_observer<R: ConversationRuntime + ?Sized>(
}

runtime
.request_turn(config, session_id, turn_id, messages, tool_view, binding)
.request_turn_with_event_sink(
config,
session_id,
turn_id,
messages,
tool_view,
acp_event_sink,
binding,
)
.await
}

Expand All @@ -2851,6 +2865,7 @@ async fn resolve_provider_turn<R: ConversationRuntime + ?Sized>(
preparation: &ProviderTurnPreparation,
result: CliResult<ProviderTurn>,
error_mode: ProviderErrorMode,
acp_event_sink: Option<&dyn AcpTurnEventSink>,
binding: ConversationRuntimeBinding<'_>,
ingress: Option<&ConversationIngressContext>,
observer: Option<&ConversationTurnObserverHandle>,
Expand Down Expand Up @@ -2901,6 +2916,7 @@ async fn resolve_provider_turn<R: ConversationRuntime + ?Sized>(
.max_discovery_followup_rounds
.saturating_add(1)
.max(1),
acp_event_sink,
binding,
observer,
)
Expand Down Expand Up @@ -3025,6 +3041,7 @@ async fn resolve_provider_turn_reply<R: ConversationRuntime + ?Sized>(
turn_loop_policy: &ProviderTurnLoopPolicy,
turn_loop_state: &mut ProviderTurnLoopState,
remaining_provider_rounds: usize,
acp_event_sink: Option<&dyn AcpTurnEventSink>,
binding: ConversationRuntimeBinding<'_>,
ingress: Option<&ConversationIngressContext>,
observer: Option<&ConversationTurnObserverHandle>,
Expand Down Expand Up @@ -3240,6 +3257,7 @@ async fn resolve_provider_turn_reply<R: ConversationRuntime + ?Sized>(
followup_preparation.turn_id.as_str(),
&followup_preparation.session.messages,
&followup_tool_view,
acp_event_sink,
binding,
observer,
)
Expand Down
7 changes: 6 additions & 1 deletion crates/app/src/memory/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@ pub use protocol::{
#[cfg(feature = "memory-sqlite")]
pub(crate) use sqlite::CanonicalMemorySearchHit;
#[cfg(feature = "memory-sqlite")]
pub use sqlite::{ConversationTurn, SqliteBootstrapDiagnostics, SqliteContextLoadDiagnostics};
pub use sqlite::ConversationSessionSummary;
#[cfg(feature = "memory-sqlite")]
pub use sqlite::{
ConversationTurn, SqliteBootstrapDiagnostics, SqliteContextLoadDiagnostics,
clear_session_direct, list_recent_sessions_direct,
};
pub use stage::{
DerivedMemoryKind, MemoryAuthority, MemoryContextProvenance, MemoryProvenanceSourceKind,
MemoryRecallMode, MemoryRecordStatus, MemoryRetrievalRequest, MemoryStageFamily,
Expand Down
Loading
Loading