@swarmclawai/swarmclaw 1.2.1 → 1.2.3
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.
- package/README.md +16 -85
- package/bin/server-cmd.js +64 -1
- package/package.json +2 -2
- package/skills/coding-agent/SKILL.md +111 -0
- package/skills/github/SKILL.md +140 -0
- package/skills/nano-banana-pro/SKILL.md +62 -0
- package/skills/nano-banana-pro/scripts/generate_image.py +235 -0
- package/skills/nano-pdf/SKILL.md +53 -0
- package/skills/openai-image-gen/SKILL.md +78 -0
- package/skills/openai-image-gen/scripts/gen.py +328 -0
- package/skills/resourceful-problem-solving/SKILL.md +49 -0
- package/skills/skill-creator/SKILL.md +147 -0
- package/skills/skill-creator/scripts/init_skill.py +378 -0
- package/skills/skill-creator/scripts/quick_validate.py +159 -0
- package/skills/summarize/SKILL.md +77 -0
- package/src/app/api/auth/route.ts +20 -5
- package/src/app/api/chats/[id]/devserver/route.ts +13 -19
- package/src/app/api/chats/[id]/messages/route.ts +13 -15
- package/src/app/api/chats/[id]/route.ts +9 -10
- package/src/app/api/chats/[id]/stop/route.ts +5 -7
- package/src/app/api/chats/messages-route.test.ts +8 -6
- package/src/app/api/chats/route.ts +9 -10
- package/src/app/api/ip/route.ts +2 -2
- package/src/app/api/preview-server/route.ts +1 -1
- package/src/app/api/projects/[id]/route.ts +7 -46
- package/src/cli/server-cmd.test.js +74 -0
- package/src/components/chat/chat-area.tsx +45 -23
- package/src/components/chat/message-bubble.test.ts +35 -0
- package/src/components/chat/message-bubble.tsx +19 -9
- package/src/components/chat/message-list.tsx +37 -3
- package/src/components/input/chat-input.tsx +34 -14
- package/src/components/openclaw/openclaw-deploy-panel.tsx +4 -0
- package/src/instrumentation.ts +1 -1
- package/src/lib/chat/assistant-render-id.ts +3 -0
- package/src/lib/chat/chat-streaming-state.test.ts +42 -3
- package/src/lib/chat/chat-streaming-state.ts +20 -8
- package/src/lib/chat/queued-message-queue.test.ts +23 -1
- package/src/lib/chat/queued-message-queue.ts +11 -2
- package/src/lib/providers/cli-utils.test.ts +124 -0
- package/src/lib/server/activity/activity-log.ts +21 -0
- package/src/lib/server/agents/agent-availability.test.ts +10 -5
- package/src/lib/server/agents/agent-cascade.ts +79 -59
- package/src/lib/server/agents/agent-registry.ts +3 -1
- package/src/lib/server/agents/agent-repository.ts +90 -0
- package/src/lib/server/agents/delegation-job-repository.ts +53 -0
- package/src/lib/server/agents/delegation-jobs.ts +11 -4
- package/src/lib/server/agents/guardian-checkpoint-repository.ts +35 -0
- package/src/lib/server/agents/guardian.ts +2 -2
- package/src/lib/server/agents/main-agent-loop.ts +10 -3
- package/src/lib/server/agents/main-loop-state-repository.ts +38 -0
- package/src/lib/server/agents/subagent-runtime.ts +9 -6
- package/src/lib/server/agents/subagent-swarm.ts +3 -2
- package/src/lib/server/agents/task-session.ts +3 -4
- package/src/lib/server/approvals/approval-repository.ts +30 -0
- package/src/lib/server/autonomy/supervisor-incident-repository.ts +42 -0
- package/src/lib/server/chat-execution/chat-execution-types.ts +38 -0
- package/src/lib/server/chat-execution/chat-execution-utils.ts +1 -1
- package/src/lib/server/chat-execution/chat-execution.ts +84 -1926
- package/src/lib/server/chat-execution/chat-turn-finalization.ts +620 -0
- package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +221 -0
- package/src/lib/server/chat-execution/chat-turn-preflight.ts +133 -0
- package/src/lib/server/chat-execution/chat-turn-preparation.ts +817 -0
- package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +296 -0
- package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +5 -5
- package/src/lib/server/chat-execution/message-classifier.test.ts +329 -0
- package/src/lib/server/chat-execution/post-stream-finalization.ts +1 -1
- package/src/lib/server/chat-execution/prompt-builder.ts +11 -0
- package/src/lib/server/chat-execution/prompt-sections.ts +5 -6
- package/src/lib/server/chat-execution/situational-awareness.ts +12 -7
- package/src/lib/server/chat-execution/stream-agent-chat.ts +16 -13
- package/src/lib/server/chatrooms/chatroom-repository.ts +32 -0
- package/src/lib/server/connectors/connector-repository.ts +58 -0
- package/src/lib/server/connectors/runtime-state.test.ts +117 -0
- package/src/lib/server/credentials/credential-repository.ts +7 -0
- package/src/lib/server/gateways/gateway-profile-repository.ts +4 -0
- package/src/lib/server/memory/memory-abstract.test.ts +59 -0
- package/src/lib/server/missions/mission-repository.ts +74 -0
- package/src/lib/server/missions/mission-service/actions.ts +6 -0
- package/src/lib/server/missions/mission-service/bindings.ts +9 -0
- package/src/lib/server/missions/mission-service/context.ts +4 -0
- package/src/lib/server/missions/mission-service/core.ts +2269 -0
- package/src/lib/server/missions/mission-service/queries.ts +12 -0
- package/src/lib/server/missions/mission-service/recovery.ts +5 -0
- package/src/lib/server/missions/mission-service/ticks.ts +9 -0
- package/src/lib/server/missions/mission-service.test.ts +9 -2
- package/src/lib/server/missions/mission-service.ts +6 -2266
- package/src/lib/server/openclaw/deploy.test.ts +42 -3
- package/src/lib/server/openclaw/deploy.ts +26 -12
- package/src/lib/server/persistence/repository-utils.ts +154 -0
- package/src/lib/server/persistence/storage-context.ts +51 -0
- package/src/lib/server/persistence/transaction.ts +1 -0
- package/src/lib/server/projects/project-repository.ts +36 -0
- package/src/lib/server/projects/project-service.ts +79 -0
- package/src/lib/server/protocols/protocol-normalization.test.ts +6 -4
- package/src/lib/server/runtime/alert-dispatch.ts +1 -1
- package/src/lib/server/runtime/daemon-policy.ts +1 -1
- package/src/lib/server/runtime/daemon-state/core.ts +1570 -0
- package/src/lib/server/runtime/daemon-state/health.ts +6 -0
- package/src/lib/server/runtime/daemon-state/policy.ts +7 -0
- package/src/lib/server/runtime/daemon-state/supervisor.ts +6 -0
- package/src/lib/server/runtime/daemon-state.test.ts +48 -0
- package/src/lib/server/runtime/daemon-state.ts +3 -1470
- package/src/lib/server/runtime/estop-repository.ts +4 -0
- package/src/lib/server/runtime/estop.ts +3 -1
- package/src/lib/server/runtime/heartbeat-service.test.ts +2 -2
- package/src/lib/server/runtime/heartbeat-service.ts +55 -34
- package/src/lib/server/runtime/heartbeat-wake.ts +6 -4
- package/src/lib/server/runtime/idle-window.ts +2 -2
- package/src/lib/server/runtime/network.ts +11 -0
- package/src/lib/server/runtime/orchestrator-events.ts +2 -2
- package/src/lib/server/runtime/queue/claims.ts +4 -0
- package/src/lib/server/runtime/queue/core.ts +2079 -0
- package/src/lib/server/runtime/queue/execution.ts +7 -0
- package/src/lib/server/runtime/queue/followups.ts +4 -0
- package/src/lib/server/runtime/queue/queries.ts +12 -0
- package/src/lib/server/runtime/queue/recovery.ts +7 -0
- package/src/lib/server/runtime/queue-recovery.test.ts +48 -13
- package/src/lib/server/runtime/queue-repository.ts +17 -0
- package/src/lib/server/runtime/queue.ts +5 -2061
- package/src/lib/server/runtime/run-ledger.ts +6 -5
- package/src/lib/server/runtime/run-repository.ts +73 -0
- package/src/lib/server/runtime/runtime-lock-repository.ts +8 -0
- package/src/lib/server/runtime/runtime-settings.ts +1 -1
- package/src/lib/server/runtime/runtime-state.ts +99 -0
- package/src/lib/server/runtime/scheduler.ts +4 -2
- package/src/lib/server/runtime/session-run-manager/cancellation.ts +157 -0
- package/src/lib/server/runtime/session-run-manager/drain.ts +246 -0
- package/src/lib/server/runtime/session-run-manager/enqueue.ts +287 -0
- package/src/lib/server/runtime/session-run-manager/queries.ts +117 -0
- package/src/lib/server/runtime/session-run-manager/recovery.ts +238 -0
- package/src/lib/server/runtime/session-run-manager/state.ts +441 -0
- package/src/lib/server/runtime/session-run-manager/types.ts +74 -0
- package/src/lib/server/runtime/session-run-manager.ts +72 -1377
- package/src/lib/server/runtime/watch-job-repository.ts +35 -0
- package/src/lib/server/runtime/watch-jobs.ts +3 -1
- package/src/lib/server/schedules/schedule-repository.ts +42 -0
- package/src/lib/server/sessions/session-repository.ts +85 -0
- package/src/lib/server/settings/settings-repository.ts +25 -0
- package/src/lib/server/skills/skill-discovery.test.ts +2 -2
- package/src/lib/server/skills/skill-discovery.ts +2 -2
- package/src/lib/server/skills/skill-repository.ts +14 -0
- package/src/lib/server/storage.ts +13 -24
- package/src/lib/server/tasks/task-repository.ts +54 -0
- package/src/lib/server/usage/usage-repository.ts +30 -0
- package/src/lib/server/webhooks/webhook-repository.ts +10 -0
- package/src/lib/strip-internal-metadata.test.ts +42 -41
- package/src/stores/use-chat-store.test.ts +54 -0
- package/src/stores/use-chat-store.ts +21 -5
- /package/{bundled-skills → skills}/google-workspace/SKILL.md +0 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Quick validation script for skills - minimal version
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
import yaml
|
|
13
|
+
except ModuleNotFoundError:
|
|
14
|
+
yaml = None
|
|
15
|
+
|
|
16
|
+
MAX_SKILL_NAME_LENGTH = 64
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _extract_frontmatter(content: str) -> Optional[str]:
|
|
20
|
+
lines = content.splitlines()
|
|
21
|
+
if not lines or lines[0].strip() != "---":
|
|
22
|
+
return None
|
|
23
|
+
for i in range(1, len(lines)):
|
|
24
|
+
if lines[i].strip() == "---":
|
|
25
|
+
return "\n".join(lines[1:i])
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _parse_simple_frontmatter(frontmatter_text: str) -> Optional[dict[str, str]]:
|
|
30
|
+
"""
|
|
31
|
+
Minimal fallback parser used when PyYAML is unavailable.
|
|
32
|
+
Supports simple `key: value` mappings used by SKILL.md frontmatter.
|
|
33
|
+
"""
|
|
34
|
+
parsed: dict[str, str] = {}
|
|
35
|
+
current_key: Optional[str] = None
|
|
36
|
+
for raw_line in frontmatter_text.splitlines():
|
|
37
|
+
stripped = raw_line.strip()
|
|
38
|
+
if not stripped or stripped.startswith("#"):
|
|
39
|
+
continue
|
|
40
|
+
|
|
41
|
+
is_indented = raw_line[:1].isspace()
|
|
42
|
+
if is_indented:
|
|
43
|
+
if current_key is None:
|
|
44
|
+
return None
|
|
45
|
+
current_value = parsed[current_key]
|
|
46
|
+
parsed[current_key] = (
|
|
47
|
+
f"{current_value}\n{stripped}" if current_value else stripped
|
|
48
|
+
)
|
|
49
|
+
continue
|
|
50
|
+
|
|
51
|
+
if ":" not in stripped:
|
|
52
|
+
return None
|
|
53
|
+
key, value = stripped.split(":", 1)
|
|
54
|
+
key = key.strip()
|
|
55
|
+
value = value.strip()
|
|
56
|
+
if not key:
|
|
57
|
+
return None
|
|
58
|
+
if (value.startswith('"') and value.endswith('"')) or (
|
|
59
|
+
value.startswith("'") and value.endswith("'")
|
|
60
|
+
):
|
|
61
|
+
value = value[1:-1]
|
|
62
|
+
parsed[key] = value
|
|
63
|
+
current_key = key
|
|
64
|
+
return parsed
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def validate_skill(skill_path):
|
|
68
|
+
"""Basic validation of a skill"""
|
|
69
|
+
skill_path = Path(skill_path)
|
|
70
|
+
|
|
71
|
+
skill_md = skill_path / "SKILL.md"
|
|
72
|
+
if not skill_md.exists():
|
|
73
|
+
return False, "SKILL.md not found"
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
content = skill_md.read_text(encoding="utf-8")
|
|
77
|
+
except OSError as e:
|
|
78
|
+
return False, f"Could not read SKILL.md: {e}"
|
|
79
|
+
|
|
80
|
+
frontmatter_text = _extract_frontmatter(content)
|
|
81
|
+
if frontmatter_text is None:
|
|
82
|
+
return False, "Invalid frontmatter format"
|
|
83
|
+
if yaml is not None:
|
|
84
|
+
try:
|
|
85
|
+
frontmatter = yaml.safe_load(frontmatter_text)
|
|
86
|
+
if not isinstance(frontmatter, dict):
|
|
87
|
+
return False, "Frontmatter must be a YAML dictionary"
|
|
88
|
+
except yaml.YAMLError as e:
|
|
89
|
+
return False, f"Invalid YAML in frontmatter: {e}"
|
|
90
|
+
else:
|
|
91
|
+
frontmatter = _parse_simple_frontmatter(frontmatter_text)
|
|
92
|
+
if frontmatter is None:
|
|
93
|
+
return (
|
|
94
|
+
False,
|
|
95
|
+
"Invalid YAML in frontmatter: unsupported syntax without PyYAML installed",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
allowed_properties = {"name", "description", "license", "allowed-tools", "metadata"}
|
|
99
|
+
|
|
100
|
+
unexpected_keys = set(frontmatter.keys()) - allowed_properties
|
|
101
|
+
if unexpected_keys:
|
|
102
|
+
allowed = ", ".join(sorted(allowed_properties))
|
|
103
|
+
unexpected = ", ".join(sorted(unexpected_keys))
|
|
104
|
+
return (
|
|
105
|
+
False,
|
|
106
|
+
f"Unexpected key(s) in SKILL.md frontmatter: {unexpected}. Allowed properties are: {allowed}",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
if "name" not in frontmatter:
|
|
110
|
+
return False, "Missing 'name' in frontmatter"
|
|
111
|
+
if "description" not in frontmatter:
|
|
112
|
+
return False, "Missing 'description' in frontmatter"
|
|
113
|
+
|
|
114
|
+
name = frontmatter.get("name", "")
|
|
115
|
+
if not isinstance(name, str):
|
|
116
|
+
return False, f"Name must be a string, got {type(name).__name__}"
|
|
117
|
+
name = name.strip()
|
|
118
|
+
if name:
|
|
119
|
+
if not re.match(r"^[a-z0-9-]+$", name):
|
|
120
|
+
return (
|
|
121
|
+
False,
|
|
122
|
+
f"Name '{name}' should be hyphen-case (lowercase letters, digits, and hyphens only)",
|
|
123
|
+
)
|
|
124
|
+
if name.startswith("-") or name.endswith("-") or "--" in name:
|
|
125
|
+
return (
|
|
126
|
+
False,
|
|
127
|
+
f"Name '{name}' cannot start/end with hyphen or contain consecutive hyphens",
|
|
128
|
+
)
|
|
129
|
+
if len(name) > MAX_SKILL_NAME_LENGTH:
|
|
130
|
+
return (
|
|
131
|
+
False,
|
|
132
|
+
f"Name is too long ({len(name)} characters). "
|
|
133
|
+
f"Maximum is {MAX_SKILL_NAME_LENGTH} characters.",
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
description = frontmatter.get("description", "")
|
|
137
|
+
if not isinstance(description, str):
|
|
138
|
+
return False, f"Description must be a string, got {type(description).__name__}"
|
|
139
|
+
description = description.strip()
|
|
140
|
+
if description:
|
|
141
|
+
if "<" in description or ">" in description:
|
|
142
|
+
return False, "Description cannot contain angle brackets (< or >)"
|
|
143
|
+
if len(description) > 1024:
|
|
144
|
+
return (
|
|
145
|
+
False,
|
|
146
|
+
f"Description is too long ({len(description)} characters). Maximum is 1024 characters.",
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
return True, "Skill is valid!"
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
if __name__ == "__main__":
|
|
153
|
+
if len(sys.argv) != 2:
|
|
154
|
+
print("Usage: python quick_validate.py <skill_directory>")
|
|
155
|
+
sys.exit(1)
|
|
156
|
+
|
|
157
|
+
valid, message = validate_skill(sys.argv[1])
|
|
158
|
+
print(message)
|
|
159
|
+
sys.exit(0 if valid else 1)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: summarize
|
|
3
|
+
description: Summarize or extract text/transcripts from URLs, podcasts, YouTube videos, and local files using the summarize CLI. Use when asked to summarize a link, article, video, or file, or to transcribe a YouTube video.
|
|
4
|
+
metadata:
|
|
5
|
+
{
|
|
6
|
+
"openclaw":
|
|
7
|
+
{
|
|
8
|
+
"emoji": "🧾",
|
|
9
|
+
"requires": { "bins": ["summarize"] },
|
|
10
|
+
"install":
|
|
11
|
+
[
|
|
12
|
+
{
|
|
13
|
+
"id": "brew",
|
|
14
|
+
"kind": "brew",
|
|
15
|
+
"formula": "steipete/tap/summarize",
|
|
16
|
+
"bins": ["summarize"],
|
|
17
|
+
"label": "Install summarize (brew)",
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# Summarize
|
|
25
|
+
|
|
26
|
+
Fast CLI to summarize URLs, local files, and YouTube links.
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
summarize "https://example.com" --model google/gemini-3-flash-preview
|
|
32
|
+
summarize "/path/to/file.pdf" --model google/gemini-3-flash-preview
|
|
33
|
+
summarize "https://youtu.be/dQw4w9WgXcQ" --youtube auto
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## YouTube: Summary vs Transcript
|
|
37
|
+
|
|
38
|
+
Best-effort transcript extraction (URLs only):
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
summarize "https://youtu.be/dQw4w9WgXcQ" --youtube auto --extract-only
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
If the user asked for a transcript but it's very long, return a tight summary first, then ask which section or time range to expand.
|
|
45
|
+
|
|
46
|
+
## Model + Keys
|
|
47
|
+
|
|
48
|
+
Set the API key for your chosen provider:
|
|
49
|
+
|
|
50
|
+
- OpenAI: `OPENAI_API_KEY`
|
|
51
|
+
- Anthropic: `ANTHROPIC_API_KEY`
|
|
52
|
+
- xAI: `XAI_API_KEY`
|
|
53
|
+
- Google: `GEMINI_API_KEY` (aliases: `GOOGLE_GENERATIVE_AI_API_KEY`, `GOOGLE_API_KEY`)
|
|
54
|
+
|
|
55
|
+
Default model is `google/gemini-3-flash-preview` if none is set.
|
|
56
|
+
|
|
57
|
+
## Useful Flags
|
|
58
|
+
|
|
59
|
+
- `--length short|medium|long|xl|xxl|<chars>` — control summary length
|
|
60
|
+
- `--max-output-tokens <count>` — hard token limit
|
|
61
|
+
- `--extract-only` — extract raw text without summarizing (URLs only)
|
|
62
|
+
- `--json` — machine-readable output
|
|
63
|
+
- `--firecrawl auto|off|always` — fallback extraction for blocked sites
|
|
64
|
+
- `--youtube auto` — Apify fallback if `APIFY_API_TOKEN` is set
|
|
65
|
+
|
|
66
|
+
## Config
|
|
67
|
+
|
|
68
|
+
Optional config file: `~/.summarize/config.json`
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{ "model": "openai/gpt-5.2" }
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Optional services:
|
|
75
|
+
|
|
76
|
+
- `FIRECRAWL_API_KEY` for blocked sites
|
|
77
|
+
- `APIFY_API_TOKEN` for YouTube fallback
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
3
|
-
import {
|
|
3
|
+
import { log } from '@/lib/server/logger'
|
|
4
|
+
import { getAccessKey, validateAccessKey, isFirstTimeSetup, markSetupComplete, replaceAccessKey } from '@/lib/server/storage-auth'
|
|
4
5
|
import { AUTH_COOKIE_NAME, getCookieValue } from '@/lib/auth'
|
|
5
6
|
import { isProductionRuntime } from '@/lib/runtime/runtime-env'
|
|
6
7
|
import { hmrSingleton } from '@/lib/shared-utils'
|
|
7
8
|
export const dynamic = 'force-dynamic'
|
|
8
9
|
|
|
10
|
+
const TAG = 'auth-route'
|
|
11
|
+
|
|
9
12
|
interface AuthAttemptEntry {
|
|
10
13
|
count: number
|
|
11
14
|
lockedUntil: number
|
|
@@ -75,6 +78,20 @@ function pruneExpiredEntries() {
|
|
|
75
78
|
}
|
|
76
79
|
}
|
|
77
80
|
|
|
81
|
+
function startDaemonAfterAuth() {
|
|
82
|
+
void import('@/lib/server/runtime/daemon-state')
|
|
83
|
+
.then(({ ensureDaemonStarted }) => {
|
|
84
|
+
try {
|
|
85
|
+
ensureDaemonStarted('api/auth:post')
|
|
86
|
+
} catch (err: unknown) {
|
|
87
|
+
log.error(TAG, 'Deferred daemon start failed', err)
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
.catch((err: unknown) => {
|
|
91
|
+
log.error(TAG, 'Failed to load daemon-state for deferred start', err)
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
78
95
|
/** POST /api/auth — validate an access key */
|
|
79
96
|
export async function POST(req: Request) {
|
|
80
97
|
const rateLimitEnabled = isRateLimitEnabled()
|
|
@@ -98,8 +115,7 @@ export async function POST(req: Request) {
|
|
|
98
115
|
replaceAccessKey(key.trim())
|
|
99
116
|
markSetupComplete()
|
|
100
117
|
if (rateLimitEnabled) authRateLimitMap.delete(clientIp)
|
|
101
|
-
|
|
102
|
-
ensureDaemonStarted('api/auth:post')
|
|
118
|
+
startDaemonAfterAuth()
|
|
103
119
|
return setAuthCookie(NextResponse.json({ ok: true }), req, key.trim())
|
|
104
120
|
}
|
|
105
121
|
|
|
@@ -128,7 +144,6 @@ export async function POST(req: Request) {
|
|
|
128
144
|
if (isFirstTimeSetup()) {
|
|
129
145
|
markSetupComplete()
|
|
130
146
|
}
|
|
131
|
-
|
|
132
|
-
ensureDaemonStarted('api/auth:post')
|
|
147
|
+
startDaemonAfterAuth()
|
|
133
148
|
return setAuthCookie(NextResponse.json({ ok: true }), req, key)
|
|
134
149
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { spawn } from 'child_process'
|
|
3
|
-
import { loadSessions, devServers, localIP } from '@/lib/server/storage'
|
|
4
3
|
import { notFound } from '@/lib/server/collection-helpers'
|
|
5
4
|
import { resolveDevServerLaunchDir } from '@/lib/server/runtime/devserver-launch'
|
|
5
|
+
import { clearDevServer, getDevServer, hasDevServer, registerDevServer, stopDevServer, updateDevServerUrl } from '@/lib/server/runtime/runtime-state'
|
|
6
|
+
import { localIP } from '@/lib/server/runtime/network'
|
|
7
|
+
import { listSessions } from '@/lib/server/sessions/session-repository'
|
|
6
8
|
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
7
9
|
import { sleep } from '@/lib/shared-utils'
|
|
8
10
|
import net from 'net'
|
|
@@ -56,22 +58,21 @@ async function startDevServer(id: string, session: { cwd: string }): Promise<Dev
|
|
|
56
58
|
if (match) {
|
|
57
59
|
const detectedPort = match[1]
|
|
58
60
|
detectedUrl = `http://${localIP()}:${detectedPort}`
|
|
59
|
-
|
|
60
|
-
if (ds) ds.url = detectedUrl
|
|
61
|
+
updateDevServerUrl(id, detectedUrl)
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
proc.stdout!.on('data', onData)
|
|
66
67
|
proc.stderr!.on('data', onData)
|
|
67
|
-
proc.on('close', () => {
|
|
68
|
-
proc.on('error', () =>
|
|
68
|
+
proc.on('close', () => { clearDevServer(id); log.info(TAG, `dev server stopped for ${id}`) })
|
|
69
|
+
proc.on('error', () => clearDevServer(id))
|
|
69
70
|
|
|
70
|
-
|
|
71
|
+
registerDevServer(id, { proc, url: `http://${localIP()}:${port}` })
|
|
71
72
|
log.info(TAG, `starting dev server in ${launch.launchDir} (session cwd=${session.cwd})`)
|
|
72
73
|
|
|
73
74
|
await sleep(4000)
|
|
74
|
-
const ds =
|
|
75
|
+
const ds = getDevServer(id)
|
|
75
76
|
if (!ds) {
|
|
76
77
|
return {
|
|
77
78
|
status: 502,
|
|
@@ -99,7 +100,7 @@ async function startDevServer(id: string, session: { cwd: string }): Promise<Dev
|
|
|
99
100
|
|
|
100
101
|
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
101
102
|
const { id } = await params
|
|
102
|
-
const sessions =
|
|
103
|
+
const sessions = listSessions()
|
|
103
104
|
const session = sessions[id]
|
|
104
105
|
if (!session) return notFound()
|
|
105
106
|
|
|
@@ -108,8 +109,8 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
108
109
|
const { action } = body
|
|
109
110
|
|
|
110
111
|
if (action === 'start') {
|
|
111
|
-
if (
|
|
112
|
-
const ds =
|
|
112
|
+
if (hasDevServer(id)) {
|
|
113
|
+
const ds = getDevServer(id)!
|
|
113
114
|
return NextResponse.json({ running: true, url: ds.url })
|
|
114
115
|
}
|
|
115
116
|
|
|
@@ -126,18 +127,11 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
126
127
|
return NextResponse.json(result.body, result.status ? { status: result.status } : undefined)
|
|
127
128
|
|
|
128
129
|
} else if (action === 'stop') {
|
|
129
|
-
|
|
130
|
-
const ds = devServers.get(id)!
|
|
131
|
-
try { ds.proc.kill('SIGTERM') } catch {}
|
|
132
|
-
if (typeof ds.proc.pid === 'number') {
|
|
133
|
-
try { process.kill(-ds.proc.pid, 'SIGTERM') } catch {}
|
|
134
|
-
}
|
|
135
|
-
devServers.delete(id)
|
|
136
|
-
}
|
|
130
|
+
stopDevServer(id)
|
|
137
131
|
return NextResponse.json({ running: false })
|
|
138
132
|
|
|
139
133
|
} else if (action === 'status') {
|
|
140
|
-
return NextResponse.json({ running:
|
|
134
|
+
return NextResponse.json({ running: hasDevServer(id), url: getDevServer(id)?.url })
|
|
141
135
|
}
|
|
142
136
|
|
|
143
137
|
return NextResponse.json({ running: false })
|
|
@@ -1,27 +1,25 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import { loadStoredItem, upsertStoredItem, active } from '@/lib/server/storage'
|
|
3
2
|
import { notFound } from '@/lib/server/collection-helpers'
|
|
4
3
|
import { materializeStreamingAssistantArtifacts } from '@/lib/chat/chat-streaming-state'
|
|
5
4
|
import { appendSessionNote } from '@/lib/server/session-note'
|
|
6
5
|
import { getSessionRunState } from '@/lib/server/runtime/session-run-manager'
|
|
7
|
-
import
|
|
6
|
+
import { getSession, saveSession } from '@/lib/server/sessions/session-repository'
|
|
7
|
+
import type { Message } from '@/types'
|
|
8
8
|
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
9
9
|
|
|
10
10
|
export async function GET(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
11
11
|
const { id } = await params
|
|
12
|
-
const session =
|
|
12
|
+
const session = getSession(id)
|
|
13
13
|
if (!session) return notFound()
|
|
14
14
|
session.messages = Array.isArray(session.messages) ? session.messages : []
|
|
15
15
|
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
// those are only computed at runtime from the active map and run ledger.
|
|
16
|
+
// Use persisted fields plus the run ledger. Process-local execution state is
|
|
17
|
+
// intentionally excluded here so stale registry entries do not block cleanup.
|
|
19
18
|
const sessionClaimsActive = session.active === true
|
|
20
19
|
|| (typeof session.currentRunId === 'string' && session.currentRunId.trim().length > 0)
|
|
21
|
-
|| active.has(id)
|
|
22
20
|
|| !!getSessionRunState(id).runningRunId
|
|
23
21
|
if (!sessionClaimsActive && materializeStreamingAssistantArtifacts(session.messages)) {
|
|
24
|
-
|
|
22
|
+
saveSession(id, session)
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
const url = new URL(req.url)
|
|
@@ -63,7 +61,7 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
63
61
|
if (error) return error
|
|
64
62
|
|
|
65
63
|
if (body.kind === 'context-clear') {
|
|
66
|
-
const session =
|
|
64
|
+
const session = getSession(id)
|
|
67
65
|
if (!session) return notFound()
|
|
68
66
|
|
|
69
67
|
session.messages.push({
|
|
@@ -72,7 +70,7 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
72
70
|
kind: 'context-clear',
|
|
73
71
|
time: Date.now(),
|
|
74
72
|
})
|
|
75
|
-
|
|
73
|
+
saveSession(id, session)
|
|
76
74
|
return NextResponse.json({ ok: true })
|
|
77
75
|
}
|
|
78
76
|
|
|
@@ -84,7 +82,7 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
84
82
|
kind: body.messageKind || 'system',
|
|
85
83
|
})
|
|
86
84
|
if (!inserted) {
|
|
87
|
-
const session =
|
|
85
|
+
const session = getSession(id)
|
|
88
86
|
if (!session) return notFound()
|
|
89
87
|
return NextResponse.json({ error: 'Note text is required' }, { status: 400 })
|
|
90
88
|
}
|
|
@@ -98,7 +96,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
98
96
|
const { id } = await params
|
|
99
97
|
const { data: body, error } = await safeParseBody<{ messageIndex: number; bookmarked: boolean }>(req)
|
|
100
98
|
if (error) return error
|
|
101
|
-
const session =
|
|
99
|
+
const session = getSession(id)
|
|
102
100
|
if (!session) return notFound()
|
|
103
101
|
|
|
104
102
|
const { messageIndex, bookmarked } = body
|
|
@@ -107,7 +105,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
107
105
|
}
|
|
108
106
|
|
|
109
107
|
session.messages[messageIndex].bookmarked = bookmarked
|
|
110
|
-
|
|
108
|
+
saveSession(id, session)
|
|
111
109
|
return NextResponse.json(session.messages[messageIndex])
|
|
112
110
|
}
|
|
113
111
|
|
|
@@ -115,7 +113,7 @@ export async function DELETE(req: Request, { params }: { params: Promise<{ id: s
|
|
|
115
113
|
const { id } = await params
|
|
116
114
|
const { data: body, error } = await safeParseBody<{ messageIndex: number }>(req)
|
|
117
115
|
if (error) return error
|
|
118
|
-
const session =
|
|
116
|
+
const session = getSession(id)
|
|
119
117
|
if (!session) return notFound()
|
|
120
118
|
|
|
121
119
|
const { messageIndex } = body
|
|
@@ -129,6 +127,6 @@ export async function DELETE(req: Request, { params }: { params: Promise<{ id: s
|
|
|
129
127
|
}
|
|
130
128
|
|
|
131
129
|
session.messages.splice(messageIndex, 1)
|
|
132
|
-
|
|
130
|
+
saveSession(id, session)
|
|
133
131
|
return NextResponse.json({ ok: true })
|
|
134
132
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import {
|
|
2
|
+
import { loadAgents } from '@/lib/server/storage'
|
|
3
3
|
import { notFound } from '@/lib/server/collection-helpers'
|
|
4
4
|
import { normalizeProviderEndpoint } from '@/lib/openclaw/openclaw-endpoint'
|
|
5
5
|
import { resolvePrimaryAgentRoute } from '@/lib/server/agents/agent-runtime-config'
|
|
6
6
|
import { clearMainLoopStateForSession } from '@/lib/server/agents/main-agent-loop'
|
|
7
7
|
import { cleanupSessionProcesses } from '@/lib/server/runtime/process-manager'
|
|
8
|
+
import { deleteSession, getSession, saveSession } from '@/lib/server/sessions/session-repository'
|
|
9
|
+
import { stopActiveSessionProcess } from '@/lib/server/runtime/runtime-state'
|
|
8
10
|
import { getSessionQueueSnapshot, getSessionRunState } from '@/lib/server/runtime/session-run-manager'
|
|
9
11
|
import { normalizeCapabilitySelection } from '@/lib/capability-selection'
|
|
10
12
|
import { enrichSessionWithMissionSummary } from '@/lib/server/missions/mission-service'
|
|
@@ -12,12 +14,12 @@ import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
|
12
14
|
|
|
13
15
|
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
14
16
|
const { id } = await params
|
|
15
|
-
const session =
|
|
17
|
+
const session = getSession(id)
|
|
16
18
|
if (!session) return notFound()
|
|
17
19
|
|
|
18
20
|
const run = getSessionRunState(id)
|
|
19
21
|
const queue = getSessionQueueSnapshot(id)
|
|
20
|
-
session.active =
|
|
22
|
+
session.active = !!run.runningRunId
|
|
21
23
|
session.queuedCount = queue.queueLength
|
|
22
24
|
session.currentRunId = run.runningRunId || null
|
|
23
25
|
|
|
@@ -28,7 +30,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
28
30
|
const { id } = await params
|
|
29
31
|
const { data: updates, error } = await safeParseBody(req)
|
|
30
32
|
if (error) return error
|
|
31
|
-
const session =
|
|
33
|
+
const session = getSession(id) as Record<string, unknown> | null
|
|
32
34
|
if (!session) return notFound()
|
|
33
35
|
|
|
34
36
|
if (updates.resetMainLoopState === true) {
|
|
@@ -142,17 +144,14 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
142
144
|
if (updates.delegateResumeIds !== undefined) session.delegateResumeIds = updates.delegateResumeIds
|
|
143
145
|
if (!Array.isArray(session.messages)) session.messages = []
|
|
144
146
|
|
|
145
|
-
|
|
147
|
+
saveSession(id, session)
|
|
146
148
|
return NextResponse.json(enrichSessionWithMissionSummary(session as never))
|
|
147
149
|
}
|
|
148
150
|
|
|
149
151
|
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
150
152
|
const { id } = await params
|
|
151
|
-
if (!
|
|
152
|
-
|
|
153
|
-
try { active.get(id)?.kill() } catch {}
|
|
154
|
-
active.delete(id)
|
|
155
|
-
}
|
|
153
|
+
if (!getSession(id)) return notFound()
|
|
154
|
+
stopActiveSessionProcess(id)
|
|
156
155
|
cleanupSessionProcesses(id)
|
|
157
156
|
deleteSession(id)
|
|
158
157
|
return new NextResponse('OK')
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { materializeStreamingAssistantArtifacts } from '@/lib/chat/chat-streaming-state'
|
|
3
|
-
import { active, loadStoredItem, upsertStoredItem } from '@/lib/server/storage'
|
|
4
3
|
import { cancelSessionRuns } from '@/lib/server/runtime/session-run-manager'
|
|
4
|
+
import { stopActiveSessionProcess } from '@/lib/server/runtime/runtime-state'
|
|
5
|
+
import { getSession, saveSession } from '@/lib/server/sessions/session-repository'
|
|
5
6
|
import type { Session } from '@/types'
|
|
6
7
|
|
|
7
8
|
export async function POST(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
8
9
|
const { id } = await params
|
|
9
10
|
const cancel = cancelSessionRuns(id, 'Stopped by user')
|
|
10
|
-
const session =
|
|
11
|
+
const session = getSession(id) as Session | null
|
|
11
12
|
if (session && Array.isArray(session.messages) && materializeStreamingAssistantArtifacts(session.messages)) {
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
if (active.has(id)) {
|
|
15
|
-
try { active.get(id)?.kill() } catch {}
|
|
16
|
-
active.delete(id)
|
|
13
|
+
saveSession(id, session)
|
|
17
14
|
}
|
|
15
|
+
stopActiveSessionProcess(id)
|
|
18
16
|
return NextResponse.json({ ok: true, ...cancel })
|
|
19
17
|
}
|
|
@@ -10,11 +10,13 @@ test('chat messages route materializes stale streaming artifacts even if runtime
|
|
|
10
10
|
returnedText: string | null
|
|
11
11
|
persistedStreaming: boolean | null
|
|
12
12
|
persistedText: string | null
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
}>(`
|
|
14
|
+
const storageMod = await import('./src/lib/server/storage')
|
|
15
|
+
const routeMod = await import('./src/app/api/chats/[id]/messages/route')
|
|
16
|
+
const runtimeStateMod = await import('./src/lib/server/runtime/runtime-state')
|
|
17
|
+
const storage = storageMod.default || storageMod
|
|
18
|
+
const route = routeMod.default || routeMod
|
|
19
|
+
const runtimeState = runtimeStateMod.default || runtimeStateMod
|
|
18
20
|
|
|
19
21
|
storage.upsertStoredItem('sessions', 'session-stale', {
|
|
20
22
|
id: 'session-stale',
|
|
@@ -37,7 +39,7 @@ test('chat messages route materializes stale streaming artifacts even if runtime
|
|
|
37
39
|
],
|
|
38
40
|
})
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
runtimeState.registerActiveSessionProcess('session-stale', { kill() {} })
|
|
41
43
|
|
|
42
44
|
const response = await route.GET(
|
|
43
45
|
new Request('http://local/api/chats/session-stale/messages'),
|
|
@@ -3,9 +3,11 @@ import { genId } from '@/lib/id'
|
|
|
3
3
|
import os from 'os'
|
|
4
4
|
import path from 'path'
|
|
5
5
|
import { perf } from '@/lib/server/runtime/perf'
|
|
6
|
-
import {
|
|
6
|
+
import { loadAgents } from '@/lib/server/storage'
|
|
7
7
|
import { WORKSPACE_DIR } from '@/lib/server/data-dir'
|
|
8
8
|
import { notify } from '@/lib/server/ws-hub'
|
|
9
|
+
import { deleteSession, listSessions, replaceSessions } from '@/lib/server/sessions/session-repository'
|
|
10
|
+
import { stopActiveSessionProcess } from '@/lib/server/runtime/runtime-state'
|
|
9
11
|
import { getSessionQueueSnapshot, getSessionRunState } from '@/lib/server/runtime/session-run-manager'
|
|
10
12
|
import { normalizeProviderEndpoint } from '@/lib/openclaw/openclaw-endpoint'
|
|
11
13
|
import { applyResolvedRoute, resolvePrimaryAgentRoute } from '@/lib/server/agents/agent-runtime-config'
|
|
@@ -25,11 +27,11 @@ export async function GET(req: Request) {
|
|
|
25
27
|
const endPerf = perf.start('api', 'GET /api/chats')
|
|
26
28
|
// Note: pruneThreadConnectorMirrors and materializeStreamingAssistantArtifacts
|
|
27
29
|
// are handled by the daemon periodic health check, not on every list fetch.
|
|
28
|
-
const sessions =
|
|
30
|
+
const sessions = listSessions()
|
|
29
31
|
for (const id of Object.keys(sessions)) {
|
|
30
32
|
const run = getSessionRunState(id)
|
|
31
33
|
const queue = getSessionQueueSnapshot(id)
|
|
32
|
-
sessions[id].active =
|
|
34
|
+
sessions[id].active = !!run.runningRunId
|
|
33
35
|
sessions[id].queuedCount = queue.queueLength
|
|
34
36
|
sessions[id].currentRunId = run.runningRunId || null
|
|
35
37
|
}
|
|
@@ -59,14 +61,11 @@ export async function DELETE(req: Request) {
|
|
|
59
61
|
if (!Array.isArray(ids) || !ids.length) {
|
|
60
62
|
return new NextResponse('Missing ids', { status: 400 })
|
|
61
63
|
}
|
|
62
|
-
const sessions =
|
|
64
|
+
const sessions = listSessions()
|
|
63
65
|
let deleted = 0
|
|
64
66
|
for (const id of ids) {
|
|
65
67
|
if (!sessions[id]) continue
|
|
66
|
-
|
|
67
|
-
try { active.get(id)?.kill() } catch {}
|
|
68
|
-
active.delete(id)
|
|
69
|
-
}
|
|
68
|
+
stopActiveSessionProcess(id)
|
|
70
69
|
deleteSession(id)
|
|
71
70
|
deleted += 1
|
|
72
71
|
}
|
|
@@ -83,7 +82,7 @@ export async function POST(req: Request) {
|
|
|
83
82
|
else if (!cwd) cwd = WORKSPACE_DIR
|
|
84
83
|
|
|
85
84
|
const id = body.id || genId()
|
|
86
|
-
const sessions =
|
|
85
|
+
const sessions = listSessions()
|
|
87
86
|
const agent = body.agentId ? loadAgents()[body.agentId] : null
|
|
88
87
|
if (isAgentDisabled(agent)) {
|
|
89
88
|
return NextResponse.json({ error: buildAgentDisabledMessage(agent, 'start chats') }, { status: 409 })
|
|
@@ -162,7 +161,7 @@ export async function POST(req: Request) {
|
|
|
162
161
|
sessions[id] = (body.provider || body.model || body.credentialId || body.apiEndpoint)
|
|
163
162
|
? nextSession
|
|
164
163
|
: applyResolvedRoute(nextSession, resolvedRoute)
|
|
165
|
-
|
|
164
|
+
replaceSessions(sessions)
|
|
166
165
|
notify('sessions')
|
|
167
166
|
return NextResponse.json(sessions[id])
|
|
168
167
|
}
|
package/src/app/api/ip/route.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import { localIP } from '@/lib/server/
|
|
2
|
+
import { localIP } from '@/lib/server/runtime/network'
|
|
3
3
|
export const dynamic = 'force-dynamic'
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
export async function GET(
|
|
6
|
+
export async function GET() {
|
|
7
7
|
return NextResponse.json({ ip: localIP(), port: parseInt(process.env.PORT || '3000') })
|
|
8
8
|
}
|
|
@@ -3,7 +3,7 @@ import { spawn, type ChildProcess } from 'child_process'
|
|
|
3
3
|
import http from 'http'
|
|
4
4
|
import fs from 'fs'
|
|
5
5
|
import path from 'path'
|
|
6
|
-
import { localIP } from '@/lib/server/
|
|
6
|
+
import { localIP } from '@/lib/server/runtime/network'
|
|
7
7
|
import { resolveDevServerLaunchDir } from '@/lib/server/runtime/devserver-launch'
|
|
8
8
|
import { resolvePathWithinBaseDir } from '@/lib/server/path-utils'
|
|
9
9
|
import { safeParseBody } from '@/lib/server/safe-parse-body'
|