慣性聚合 高效追讀感興趣之博客、新聞、科技資訊
閱原文 以慣性聚合開啟

推薦訂閱源

博客园 - 司徒正美
V
V2EX
T
Tailwind CSS Blog
有赞技术团队
有赞技术团队
aimingoo的专栏
aimingoo的专栏
Apple Machine Learning Research
Apple Machine Learning Research
IT之家
IT之家
Blog — PlanetScale
Blog — PlanetScale
A
About on SuperTechFans
月光博客
月光博客
T
The Blog of Author Tim Ferriss
宝玉的分享
宝玉的分享
Martin Fowler
Martin Fowler
博客园 - 聂微东
The GitHub Blog
The GitHub Blog
V
Visual Studio Blog
WordPress大学
WordPress大学
酷 壳 – CoolShell
酷 壳 – CoolShell
Engineering at Meta
Engineering at Meta
GbyAI
GbyAI

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4 test: trim doctor command hotspots · openclaw/openclaw@c66f16a test: isolate agent auth and spawn hotspots · openclaw/openclaw@9285935 test: stabilize MCP startup disposal race · openclaw/openclaw@dd9d2eb test: merge browser contract server suites · openclaw/openclaw@5817a76 test: narrow ollama provider discovery setup · openclaw/openclaw@a0d9598 build: declare qa-lab aimock runtime dependency · openclaw/openclaw@24431e5 test: speed up safe-bins exec harness · openclaw/openclaw@ee856ab test: preserve tool helpers in embedded runner mocks · openclaw/openclaw@acd86a0 refactor: move memory embeddings into provider plugins · openclaw/openclaw@77e6e4c test: reuse system-run temp fixtures · openclaw/openclaw@7e9ff0f test: trim hotspot wait overhead · openclaw/openclaw@12a59b0 Check: avoid duplicate boundary prep · openclaw/openclaw@baf11b8 test: reduce hotspot fixture overhead · openclaw/openclaw@3a59edd feat(ui): overhaul settings and slash command UX (#67819) thanks @Bun… · openclaw/openclaw@2cfb660 QA Matrix: exit cleanly on failure · openclaw/openclaw@42805d2 QA Matrix: isolate scenario coverage · openclaw/openclaw@7e659e1 Matrix: refresh crypto bootstrap state · openclaw/openclaw@94081d8 QA Lab: add provider registry · openclaw/openclaw@bb7e982 Matrix: add plugin changelog · openclaw/openclaw@4acab55 test: trim more hotspot overhead · openclaw/openclaw@f485311
功于言:增实时主动运行控制 · openclaw/openclaw@bbf9c45
steipete · 2026-05-24 · via Recent Commits to openclaw:main
原本之文行序差异之文行序差异之文行变

@@ -79,6 +79,9 @@ internal data class RealtimeToolRun(

7979

val relaySessionId: String,

8080

)

8181
82+

private const val REALTIME_AGENT_CONSULT_TOOL = "openclaw_agent_consult"

83+

private const val REALTIME_AGENT_CONTROL_TOOL = "openclaw_agent_control"

84+
8285

private data class RealtimeToolCompletion(

8386

val state: String,

8487

val messageEl: JsonElement?,

@@ -1052,6 +1055,10 @@ class TalkModeManager internal constructor(

10521055

pendingRealtimeToolCalls.add(callId)

10531056

scope.launch {

10541057

try {

1058+

if (name == REALTIME_AGENT_CONTROL_TOOL) {

1059+

submitRealtimeAgentControl(callId = callId, relaySessionId = relaySessionId, args = args)

1060+

return@launch

1061+

}

10551062

if (forced) {

10561063

submitRealtimeToolWorking(callId, relaySessionId)

10571064

}

@@ -1183,7 +1190,7 @@ class TalkModeManager internal constructor(

11831190

result =

11841191

buildJsonObject {

11851192

put("status", JsonPrimitive("working"))

1186-

put("tool", JsonPrimitive("openclaw_agent_consult"))

1193+

put("tool", JsonPrimitive(REALTIME_AGENT_CONSULT_TOOL))

11871194

put(

11881195

"message",

11891196

JsonPrimitive(

@@ -1195,6 +1202,30 @@ class TalkModeManager internal constructor(

11951202

)

11961203

}

11971204
1205+

private suspend fun submitRealtimeAgentControl(

1206+

callId: String,

1207+

relaySessionId: String,

1208+

args: JsonElement?,

1209+

) {

1210+

val argsObject = args.asObjectOrNull()

1211+

val text = argsObject?.get("text").asStringOrNull()?.trim().orEmpty()

1212+

val mode = argsObject?.get("mode").asStringOrNull()?.trim()

1213+

val params =

1214+

buildJsonObject {

1215+

put("sessionId", JsonPrimitive(relaySessionId))

1216+

put("sessionKey", JsonPrimitive(mainSessionKey.ifBlank { "main" }))

1217+

put("text", JsonPrimitive(text.ifEmpty { "status" }))

1218+

if (!mode.isNullOrEmpty()) put("mode", JsonPrimitive(mode))

1219+

}

1220+

val response = session.request("talk.session.steer", params.toString(), timeoutMs = 15_000)

1221+

val result = json.parseToJsonElement(response).asObjectOrNull()

1222+

if (result != null) {

1223+

submitRealtimeToolResult(callId = callId, result = result, sessionId = relaySessionId)

1224+

} else {

1225+

submitRealtimeToolError(callId, "control call returned no result", relaySessionId)

1226+

}

1227+

}

1228+
11981229

private fun upsertRealtimeConversation(

11991230

role: VoiceConversationRole,

12001231

text: String,

原本之文行序差异之文行序差异之文行变

@@ -83,6 +83,8 @@ private actor RealtimeAudioSender {

8383
8484

@MainActor

8585

final class RealtimeTalkRelaySession {

86+

private static let agentControlToolName = "openclaw_agent_control"

87+
8688

struct Options {

8789

let sessionKey: String

8890

let provider: String?

@@ -330,6 +332,13 @@ final class RealtimeTalkRelaySession {

330332

else { return }

331333

self.onStatus("Thinking…")

332334

do {

335+

if name == Self.agentControlToolName {

336+

try await self.handleAgentControlToolCall(

337+

callId: callId,

338+

relaySessionId: relaySessionId,

339+

args: payload["args"])

340+

return

341+

}

333342

let completionStream = await self.gateway.subscribeServerEvents(bufferingNewest: 200)

334343

let args = payload["args"]?.foundationValue ?? [:]

335344

let startPayload: [String: Any] = [

@@ -366,6 +375,34 @@ final class RealtimeTalkRelaySession {

366375

}

367376

}

368377
378+

private func handleAgentControlToolCall(

379+

callId: String,

380+

relaySessionId: String,

381+

args: AnyCodable?) async throws

382+

{

383+

let controlArgs = args?.dictionaryValue ?? [:]

384+

var payload: [String: Any] = [

385+

"sessionId": relaySessionId,

386+

"sessionKey": self.options.sessionKey,

387+

"text": controlArgs["text"]?.stringValue?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "status",

388+

]

389+

if let mode = controlArgs["mode"]?.stringValue?.trimmingCharacters(in: .whitespacesAndNewlines),

390+

!mode.isEmpty

391+

{

392+

payload["mode"] = mode

393+

}

394+

let response = try await self.requestJSON(

395+

method: "talk.session.steer",

396+

payload: payload,

397+

decodeAs: AnyCodable.self,

398+

timeoutSeconds: 30)

399+

let result = response.dictionaryValue?.mapValues(\.foundationValue) ?? [

400+

"result": response.foundationValue,

401+

]

402+

try await self.submitToolResult(callId: callId, result: result)

403+

self.onStatus("Listening (Realtime)")

404+

}

405+
369406

private func submitToolResult(callId: String, result: [String: Any]) async throws {

370407

guard let relaySessionId else { return }

371408

let payload: [String: Any] = [

原本之文行序差异之文行序差异之文行变

@@ -3174,6 +3174,102 @@ public struct TalkClientCreateParams: Codable, Sendable {

31743174

}

31753175

}

31763176
3177+

public struct TalkClientSteerParams: Codable, Sendable {

3178+

public let sessionkey: String

3179+

public let text: String

3180+

public let mode: AnyCodable?

3181+
3182+

public init(

3183+

sessionkey: String,

3184+

text: String,

3185+

mode: AnyCodable?)

3186+

{

3187+

self.sessionkey = sessionkey

3188+

self.text = text

3189+

self.mode = mode

3190+

}

3191+
3192+

private enum CodingKeys: String, CodingKey {

3193+

case sessionkey = "sessionKey"

3194+

case text

3195+

case mode

3196+

}

3197+

}

3198+
3199+

public struct TalkAgentControlResult: Codable, Sendable {

3200+

public let ok: Bool

3201+

public let mode: AnyCodable

3202+

public let sessionkey: String

3203+

public let sessionid: String?

3204+

public let active: Bool

3205+

public let queued: Bool?

3206+

public let aborted: Bool?

3207+

public let target: AnyCodable?

3208+

public let reason: String?

3209+

public let message: String

3210+

public let speak: Bool

3211+

public let show: Bool

3212+

public let suppress: Bool

3213+

public let providerresult: [String: AnyCodable]?

3214+

public let enqueuedatms: Double?

3215+

public let deliveredatms: Double?

3216+
3217+

public init(

3218+

ok: Bool,

3219+

mode: AnyCodable,

3220+

sessionkey: String,

3221+

sessionid: String?,

3222+

active: Bool,

3223+

queued: Bool?,

3224+

aborted: Bool?,

3225+

target: AnyCodable?,

3226+

reason: String?,

3227+

message: String,

3228+

speak: Bool,

3229+

show: Bool,

3230+

suppress: Bool,

3231+

providerresult: [String: AnyCodable]?,

3232+

enqueuedatms: Double?,

3233+

deliveredatms: Double?)

3234+

{

3235+

self.ok = ok

3236+

self.mode = mode

3237+

self.sessionkey = sessionkey

3238+

self.sessionid = sessionid

3239+

self.active = active

3240+

self.queued = queued

3241+

self.aborted = aborted

3242+

self.target = target

3243+

self.reason = reason

3244+

self.message = message

3245+

self.speak = speak

3246+

self.show = show

3247+

self.suppress = suppress

3248+

self.providerresult = providerresult

3249+

self.enqueuedatms = enqueuedatms

3250+

self.deliveredatms = deliveredatms

3251+

}

3252+
3253+

private enum CodingKeys: String, CodingKey {

3254+

case ok

3255+

case mode

3256+

case sessionkey = "sessionKey"

3257+

case sessionid = "sessionId"

3258+

case active

3259+

case queued

3260+

case aborted

3261+

case target

3262+

case reason

3263+

case message

3264+

case speak

3265+

case show

3266+

case suppress

3267+

case providerresult = "providerResult"

3268+

case enqueuedatms = "enqueuedAtMs"

3269+

case deliveredatms = "deliveredAtMs"

3270+

}

3271+

}

3272+
31773273

public struct TalkClientToolCallParams: Codable, Sendable {

31783274

public let sessionkey: String

31793275

public let callid: String

@@ -3580,6 +3676,32 @@ public struct TalkSessionTurnResult: Codable, Sendable {

35803676

}

35813677

}

35823678
3679+

public struct TalkSessionSteerParams: Codable, Sendable {

3680+

public let sessionid: String

3681+

public let sessionkey: String?

3682+

public let text: String

3683+

public let mode: AnyCodable?

3684+
3685+

public init(

3686+

sessionid: String,

3687+

sessionkey: String?,

3688+

text: String,

3689+

mode: AnyCodable?)

3690+

{

3691+

self.sessionid = sessionid

3692+

self.sessionkey = sessionkey

3693+

self.text = text

3694+

self.mode = mode

3695+

}

3696+
3697+

private enum CodingKeys: String, CodingKey {

3698+

case sessionid = "sessionId"

3699+

case sessionkey = "sessionKey"

3700+

case text

3701+

case mode

3702+

}

3703+

}

3704+
35833705

public struct TalkSessionSubmitToolResultParams: Codable, Sendable {

35843706

public let sessionid: String

35853707

public let callid: String

原本之文行序差异之文行序差异行变更

@@ -55,6 +55,7 @@ describe("method scope resolution", () => {

5555

["poll", ["operator.write"]],

5656

["talk.client.create", ["operator.write"]],

5757

["talk.client.toolCall", ["operator.write"]],

58+

["talk.client.steer", ["operator.write"]],

5859

["talk.session.create", ["operator.write"]],

5960

["talk.session.join", ["operator.write"]],

6061

["talk.session.appendAudio", ["operator.write"]],

@@ -63,6 +64,7 @@ describe("method scope resolution", () => {

6364

["talk.session.cancelTurn", ["operator.write"]],

6465

["talk.session.cancelOutput", ["operator.write"]],

6566

["talk.session.submitToolResult", ["operator.write"]],

67+

["talk.session.steer", ["operator.write"]],

6668

["talk.session.close", ["operator.write"]],

6769

["update.status", ["operator.admin"]],

6870

["config.schema", ["operator.admin"]],

@@ -244,6 +246,7 @@ describe("operator scope authorization", () => {

244246

for (const method of [

245247

"talk.client.create",

246248

"talk.client.toolCall",

249+

"talk.client.steer",

247250

"talk.session.create",

248251

"talk.session.join",

249252

"talk.session.appendAudio",

@@ -252,6 +255,7 @@ describe("operator scope authorization", () => {

252255

"talk.session.cancelTurn",

253256

"talk.session.cancelOutput",

254257

"talk.session.submitToolResult",

258+

"talk.session.steer",

255259

"talk.session.close",

256260

]) {

257261

expect(authorizeOperatorScopesForMethod(method, ["operator.write"])).toEqual({

原文件行号差异行号差异行变更

@@ -71,6 +71,7 @@ export const CORE_GATEWAY_METHOD_SPECS: readonly CoreGatewayMethodSpec[] = [

7171

{ name: "talk.config", scope: "operator.read" },

7272

{ name: "talk.client.create", scope: "operator.write" },

7373

{ name: "talk.client.toolCall", scope: "operator.write" },

74+

{ name: "talk.client.steer", scope: "operator.write" },

7475

{ name: "talk.session.create", scope: "operator.write" },

7576

{ name: "talk.session.join", scope: "operator.write" },

7677

{ name: "talk.session.appendAudio", scope: "operator.write" },

@@ -79,6 +80,7 @@ export const CORE_GATEWAY_METHOD_SPECS: readonly CoreGatewayMethodSpec[] = [

7980

{ name: "talk.session.cancelTurn", scope: "operator.write" },

8081

{ name: "talk.session.cancelOutput", scope: "operator.write" },

8182

{ name: "talk.session.submitToolResult", scope: "operator.write" },

83+

{ name: "talk.session.steer", scope: "operator.write" },

8284

{ name: "talk.session.close", scope: "operator.write" },

8385

{ name: "talk.speak", scope: "operator.write" },

8486

{ name: "talk.mode", scope: "operator.write" },

原文件行号差异行号差异行变更

@@ -16,14 +16,17 @@ import {

1616

validateTalkConfigResult,

1717

validateTalkEvent,

1818

validateTalkClientCreateParams,

19+

validateTalkClientSteerParams,

1920

validateTalkClientToolCallParams,

21+

validateTalkAgentControlResult,

2022

validateTalkSessionAppendAudioParams,

2123

validateTalkSessionCancelOutputParams,

2224

validateTalkSessionCancelTurnParams,

2325

validateTalkSessionCreateParams,

2426

validateTalkSessionJoinParams,

2527

validateTalkSessionJoinResult,

2628

validateTalkSessionSubmitToolResultParams,

29+

validateTalkSessionSteerParams,

2730

validateTalkSessionTurnParams,

2831

validateTalkSessionTurnResult,

2932

validateWakeParams,

@@ -454,6 +457,44 @@ describe("validateTalkClientToolCallParams", () => {

454457

});

455458

});

456459
460+

describe("validateTalkAgentControlParams", () => {

461+

it("accepts client and session steering params plus structured outcomes", () => {

462+

expect(

463+

validateTalkClientSteerParams({

464+

sessionKey: "agent:main:main",

465+

text: "use the safer path",

466+

mode: "steer",

467+

}),

468+

).toBe(true);

469+

expect(

470+

validateTalkSessionSteerParams({

471+

sessionId: "talk-1",

472+

sessionKey: "agent:main:main",

473+

text: "status",

474+

mode: "status",

475+

}),

476+

).toBe(true);

477+

expect(

478+

validateTalkAgentControlResult({

479+

ok: true,

480+

mode: "cancel",

481+

sessionKey: "agent:main:main",

482+

sessionId: "session-1",

483+

active: true,

484+

aborted: true,

485+

message: "Cancelled the active OpenClaw run.",

486+

speak: true,

487+

show: true,

488+

suppress: false,

489+

providerResult: {

490+

status: "cancelled",

491+

message: "Cancelled the active OpenClaw run.",

492+

},

493+

}),

494+

).toBe(true);

495+

});

496+

});

497+
457498

describe("validateTalkSessionRelayParams", () => {

458499

it("accepts session audio, cancel, output cancel, and tool result params", () => {

459500

expect(