Skip to content

Commit f01b403

Browse files
Merge branch 'main' into fix/problem-codex-dynamic-tool-log-utf16
2 parents 7ac059d + f0d4d4f commit f01b403

91 files changed

Lines changed: 1771 additions & 552 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/skills/openclaw-landable-bug-sweep/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ Reject:
8888
- include PRs linked from issues and duplicates
8989
3. For each cluster:
9090
- read issue/PR body, comments, labels, linked refs, current source, adjacent tests
91-
- suppress maintainer-owned queue noise unless it is the best fix path
91+
- exclude PRs authored by wide-access maintainers until `created_at` is at least 14 days old; only a named PR or explicit maintainer-work request overrides
9292
- identify opener/author and preserve credit
9393
- decide: `repair-existing-pr`, `create-new-pr`, `close-fixed-on-main`, `close-duplicate`, or `reject`
9494
4. Prove before patching:

.agents/skills/openclaw-pr-maintainer/SKILL.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,9 @@ gh api -X POST "repos/openclaw/openclaw/issues/<number>/assignees" -f 'assignees
7676
- If `name` is empty, use the login only. If profile lookup is rate-limited or unavailable, say `account age unknown` rather than omitting the opener.
7777
- Use identity and activity as triage signal, not proof by itself: new, low-activity, or bot-like accounts can raise review caution, but code, repro, and CI evidence still decide.
7878

79-
## Suppress top-maintainer items in issue triage
79+
## Suppress recent wide-access maintainer PRs in triage
8080

81-
When asked for issue triage, hot issues, pressing bugs, Discord-correlated issues, or "what is still open", do not surface issues or PRs authored by top maintainers by default. Prefer external/user-reported hot issues and external PRs, not maintainer-owned work queues.
81+
In generic issue/PR triage, hot queues, landable shortlists, or "what is still open", exclude PRs authored by maintainers with broad repository access until 14 days after `created_at`. Prefer external contributors' PRs. An ordinary request for landing candidates does not override the age gate. Continue suppressing maintainer-authored issues by default.
8282

8383
Suppress by default when the opener/author is one of:
8484

@@ -107,8 +107,9 @@ Also suppress lower-priority maintainer-owned noise from the broader keep/top-ma
107107

108108
Exceptions:
109109

110-
- Show maintainer-authored items when the requester explicitly asks for maintainer PRs/issues, PR landing candidates, release-blocking maintainer work, or a specific PR/issue number.
111-
- Show a maintainer-authored item when it is the canonical fix for an external hot issue, but frame it as the fix path rather than as a user-facing issue candidate.
110+
- Once a maintainer-authored PR is at least 14 days old, triage it normally.
111+
- A specific PR/issue number or an explicit request for maintainer-owned work overrides suppression.
112+
- When a recent maintainer PR is the canonical fix for an external hot issue, mention it only as the fix path; do not count it as a triage or landing candidate.
112113
- Do not close, label, or deprioritize solely because an item is maintainer-authored; this section only controls what appears in triage shortlists.
113114

114115
## Apply close and triage labels correctly

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ Skills own workflows; root owns hard policy and routing.
159159
- PR superseded by `main`: if code proof shows `main` already has same-or-better behavior, comment canonical commit/PR + focused proof, then close. Bar high: inspect PR diff, current code/tests, linked issue, caller/sibling path. If unsure, leave open.
160160
- Issue/PR numbers need a short summary every time; assume the reader has not opened or read them.
161161
- Before presenting a batch of issues/PRs, use smart subagents to verify live state and current `main`; omit closed/fixed items, and comment+close items already fixed on `main` when maintainer action is authorized.
162+
- Generic triage and landing shortlists: exclude PRs authored by maintainers with broad repository access until 14 days after creation. An ordinary request for landing candidates does not override this gate; only a named PR or explicit request for maintainer-owned work does.
162163
- PR review answer: bug/behavior, URL(s), affected surface, provenance for regressions when traceable, best-fix judgment, evidence from code/tests/CI/current or shipped behavior.
163164
- PR reviewable findings: post them on the PR, not chat-only, so author sees actionable feedback.
164165
- Issue/PR final answer: last line is the full GitHub URL.

apps/android/Config/Version.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
# Generated by scripts/android-sync-versioning.ts.
44

55
OPENCLAW_ANDROID_VERSION_NAME=2026.7.1
6-
OPENCLAW_ANDROID_VERSION_CODE=2026070101
6+
OPENCLAW_ANDROID_VERSION_CODE=2026070102

apps/android/app/src/main/java/ai/openclaw/app/node/DeviceNotificationListenerService.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,18 @@ private const val NOTIFICATIONS_CHANGED_EVENT = "notifications.changed"
2727
internal fun sanitizeNotificationText(value: CharSequence?): String? {
2828
val normalized = value?.toString()?.trim().orEmpty()
2929
// Notification extras can include long previews; cap before sending over node events.
30-
return normalized.take(MAX_NOTIFICATION_TEXT_CHARS).ifEmpty { null }
30+
return normalized.takeUtf16Safe(MAX_NOTIFICATION_TEXT_CHARS).ifEmpty { null }
31+
}
32+
33+
private fun String.takeUtf16Safe(maxChars: Int): String {
34+
if (length <= maxChars) return this
35+
val end =
36+
if (maxChars > 0 && Character.isHighSurrogate(this[maxChars - 1])) {
37+
maxChars - 1
38+
} else {
39+
maxChars
40+
}
41+
return take(end)
3142
}
3243

3344
/**

apps/android/app/src/test/java/ai/openclaw/app/node/NotificationsHandlerTest.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,18 @@ class NotificationsHandlerTest {
265265
assertTrue((sanitized ?: "").all { it == 'x' })
266266
}
267267

268+
@Test
269+
fun sanitizeNotificationTextPreservesUtf16BoundariesAtLimit() {
270+
val splitPairPrefix = "a".repeat(511)
271+
assertEquals(splitPairPrefix, sanitizeNotificationText("$splitPairPrefix🚀 trailing text"))
272+
273+
val completePairPrefix = "a".repeat(510)
274+
assertEquals(
275+
"$completePairPrefix🚀",
276+
sanitizeNotificationText("$completePairPrefix🚀 trailing text"),
277+
)
278+
}
279+
268280
@Test
269281
fun notificationsActionClearablePolicy_onlyRequiresClearableForDismiss() {
270282
assertTrue(actionRequiresClearableNotification(NotificationActionKind.Dismiss))

apps/android/gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ androidx-exifinterface = "1.4.2"
99
androidx-lifecycle = "2.11.0"
1010
androidx-security = "1.1.0"
1111
androidx-test-ext = "1.3.0"
12-
androidx-uiautomator = "2.4.0-beta02"
12+
androidx-uiautomator = "2.4.0"
1313
androidx-webkit = "1.15.0"
1414
bcprov = "1.84"
1515
commonmark = "0.29.0"

apps/android/version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
22
"version": "2026.7.1",
3-
"versionCode": 2026070101
3+
"versionCode": 2026070102
44
}

apps/macos/Sources/OpenClaw/LaunchAgentManager.swift

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,32 @@ enum LaunchAgentManager {
88

99
static func status() async -> Bool {
1010
guard FileManager().fileExists(atPath: self.plistURL.path) else { return false }
11+
return await self.isLoaded()
12+
}
13+
14+
private static func isLoaded() async -> Bool {
1115
let result = await self.runLaunchctl(["print", "gui/\(getuid())/\(launchdLabel)"])
1216
return result == 0
1317
}
1418

15-
static func set(enabled: Bool, bundlePath: String) async {
19+
@discardableResult
20+
static func set(
21+
enabled: Bool,
22+
bundlePath: String,
23+
loaded: Bool? = nil,
24+
writePlist: ((String) -> Void)? = nil) async -> Bool
25+
{
1626
if enabled {
17-
self.writePlist(bundlePath: bundlePath)
27+
let persist = writePlist ?? { self.writePlist(bundlePath: $0) }
28+
persist(bundlePath)
29+
let alreadyLoaded = if let loaded {
30+
loaded
31+
} else {
32+
await self.isLoaded()
33+
}
34+
// Startup hydrates the toggle from launchd. Reinstalling the active job here
35+
// would boot out the app that is still responsible for bootstrapping it again.
36+
guard !alreadyLoaded else { return false }
1837
_ = await self.runLaunchctl(["bootout", "gui/\(getuid())/\(launchdLabel)"])
1938
_ = await self.runLaunchctl(["bootstrap", "gui/\(getuid())", self.plistURL.path])
2039
_ = await self.runLaunchctl(["kickstart", "-k", "gui/\(getuid())/\(launchdLabel)"])
@@ -23,6 +42,7 @@ enum LaunchAgentManager {
2342
// bootout would terminate the launchd job immediately (and crash the app if launched via agent).
2443
try? FileManager().removeItem(at: self.plistURL)
2544
}
45+
return true
2646
}
2747

2848
private static func writePlist(bundlePath: String) {

apps/macos/Tests/OpenClawIPCTests/LaunchAgentManagerTests.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,18 @@ import Testing
33
@testable import OpenClaw
44

55
struct LaunchAgentManagerTests {
6+
@Test func `enabling an already loaded login job only refreshes its plist`() async {
7+
var persistedBundlePaths: [String] = []
8+
let reloaded = await LaunchAgentManager.set(
9+
enabled: true,
10+
bundlePath: "/Applications/OpenClaw.app",
11+
loaded: true,
12+
writePlist: { persistedBundlePaths.append($0) })
13+
14+
#expect(reloaded == false)
15+
#expect(persistedBundlePaths == ["/Applications/OpenClaw.app"])
16+
}
17+
618
@Test func `launch at login plist does not keep app alive after manual quit`() throws {
719
let plist = LaunchAgentManager.plistContents(bundlePath: "/Applications/OpenClaw.app")
820
let data = try #require(plist.data(using: .utf8))

0 commit comments

Comments
 (0)