refactor: share native approval route gates

Share native approval route gate helpers across mainstream channel approval runtimes and keep PR #87770 green on current main.
This commit is contained in:
Kevin Lin
2026-05-29 15:32:31 -07:00
committed by GitHub
parent 44e31f7c6a
commit c57671176e
68 changed files with 737 additions and 422 deletions

View File

@@ -1,2 +1,2 @@
74c6b635c822358e61473a5b1bf7e6786592c459289057db2fddf807ffa022ec plugin-sdk-api-baseline.json
4db87f9b57209632cd3887d791277f6c30c4a894b09785a2b51cb50cb5aedb78 plugin-sdk-api-baseline.jsonl
4272d9b4edc35fbdf9e1980c109845f59283a5c1e120235fb158a9db3c054b01 plugin-sdk-api-baseline.json
e24313a7fadaad09b58c1b683e2906973ff01d8b1bb3f403b9cef30bdcb89ee6 plugin-sdk-api-baseline.jsonl

View File

@@ -155,6 +155,7 @@ Most channel plugins do not need approval-specific code.
- If custom approval auth intentionally allows only same-chat fallback, return `markImplicitSameChatApprovalAuthorization({ authorized: true })` from `openclaw/plugin-sdk/approval-auth-runtime`; otherwise core treats the result as explicit approver authorization.
- If a channel-owned native callback resolves approvals directly, use `isImplicitSameChatApprovalAuthorization(...)` before resolving so implicit fallback still goes through the channel's normal actor authorization.
- If a channel needs native approval delivery, keep channel code focused on target normalization plus transport/presentation facts. Use `createChannelExecApprovalProfile`, `createChannelNativeOriginTargetResolver`, `createChannelApproverDmTargetResolver`, and `createApproverRestrictedNativeApprovalCapability` from `openclaw/plugin-sdk/approval-runtime`. Put the channel-specific facts behind `approvalCapability.nativeRuntime`, ideally via `createChannelApprovalNativeRuntimeAdapter(...)` or `createLazyChannelApprovalNativeRuntimeAdapter(...)`, so core can assemble the handler and own request filtering, routing, dedupe, expiry, gateway subscription, and routed-elsewhere notices. `nativeRuntime` is split into a few smaller seams:
- Use `createNativeApprovalChannelRouteGates` from `openclaw/plugin-sdk/approval-native-runtime` when a channel supports both session-origin native delivery and explicit approval forwarding targets. The helper centralizes approval config selection, `mode` handling, agent/session filters, account binding, session-target matching, and target-list matching while callers still own the channel id, default forwarding mode, account lookup, transport-enabled check, target normalization, and turn-source target resolution. Do not use it to create core-owned channel policy defaults; pass the channel's documented default mode explicitly.
- `createChannelNativeOriginTargetResolver` uses the shared channel-route matcher by default for `{ to, accountId, threadId }` targets. Pass `targetsMatch` only when a channel has provider-specific equivalence rules, such as Slack timestamp prefix matching.
- Pass `normalizeTargetForMatch` to `createChannelNativeOriginTargetResolver` when the channel needs to canonicalize provider ids before the default route matcher or a custom `targetsMatch` callback runs, while preserving the original target for delivery. Use `normalizeTarget` only when the resolved delivery target itself should be canonicalized.
- `availability` - whether the account is configured and whether a request should be handled

View File

@@ -179,7 +179,7 @@ and pairing-path families.
| `plugin-sdk/approval-gateway-runtime` | Shared approval gateway-resolution helper |
| `plugin-sdk/approval-handler-adapter-runtime` | Lightweight native approval adapter loading helpers for hot channel entrypoints |
| `plugin-sdk/approval-handler-runtime` | Broader approval handler runtime helpers; prefer the narrower adapter/gateway seams when they are enough |
| `plugin-sdk/approval-native-runtime` | Native approval target + account-binding helpers and local native exec prompt suppression |
| `plugin-sdk/approval-native-runtime` | Native approval target, account-binding, route-gate, forwarding fallback, and local native exec prompt suppression helpers |
| `plugin-sdk/approval-reaction-runtime` | Hardcoded approval reaction bindings, reaction prompt payloads, reaction target stores, and compatibility export for local native exec prompt suppression |
| `plugin-sdk/approval-reply-runtime` | Exec/plugin approval reply payload helpers |
| `plugin-sdk/approval-runtime` | Exec/plugin approval payload helpers, native approval routing/runtime helpers, and structured approval display helpers such as `formatApprovalDisplayPath` |

View File

@@ -79,6 +79,18 @@ function buildConversationKeyForTarget(to: string): IMessageApprovalConversation
}
}
function shouldThreadApprovalUpdate(to: string): boolean {
try {
const parsed = parseIMessageTarget(to);
if (parsed.kind === "handle" && parsed.service === "sms") {
return false;
}
} catch {
return true;
}
return true;
}
export const imessageApprovalNativeRuntime = createChannelApprovalNativeRuntimeAdapter<
IMessagePendingDelivery,
PreparedIMessageApprovalTarget,
@@ -151,7 +163,7 @@ export const imessageApprovalNativeRuntime = createChannelApprovalNativeRuntimeA
await sendMessageIMessage(entry.to, payload.text, {
config: cfg,
...(entry.accountId ? { accountId: entry.accountId } : {}),
replyToId: entry.messageId,
...(shouldThreadApprovalUpdate(entry.to) ? { replyToId: entry.messageId } : {}),
});
},
},

View File

@@ -5,12 +5,10 @@ import {
import { createLazyChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
import type { ChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-runtime";
import {
createChannelApprovalForwardingEvaluator,
createChannelApproverDmTargetResolver,
createChannelNativeOriginTargetResolver,
createNativeApprovalChannelRouteGates,
createNativeApprovalForwardingFallbackSuppressor,
nativeApprovalTargetsMatch,
resolveApprovalRequestSessionTarget,
shouldSuppressLocalNativeExecApprovalPrompt,
} from "openclaw/plugin-sdk/approval-native-runtime";
import {
@@ -48,8 +46,8 @@ import { normalizeIMessageMessagingTarget } from "./normalize.js";
import { inferIMessageTargetChatType } from "./targets.js";
type ApprovalRequest = ExecApprovalRequest | PluginApprovalRequest;
type ApprovalKind = "exec" | "plugin";
type ApprovalForwardingConfig = NonNullable<NonNullable<OpenClawConfig["approvals"]>["exec"]>;
type ApprovalForwardingMode = NonNullable<ApprovalForwardingConfig["mode"]>;
type ChannelApprovalForwardTarget = Parameters<
NonNullable<
NonNullable<ChannelApprovalCapability["delivery"]>["shouldSuppressForwardingFallback"]
@@ -61,6 +59,7 @@ type IMessageApprovalTarget = {
threadId?: string | number | null;
};
const DEFAULT_APPROVAL_FORWARDING_MODE: ApprovalForwardingMode = "session";
const DEFAULT_PLUGIN_APPROVAL_DECISIONS: readonly ExecApprovalReplyDecision[] = [
"allow-once",
"allow-always",
@@ -74,35 +73,6 @@ function isIMessageApprovalTransportEnabled(params: {
return resolveIMessageAccount({ cfg: params.cfg, accountId: params.accountId }).enabled;
}
function targetAccountMatchesIMessageAccount(params: {
cfg: OpenClawConfig;
targetAccountId?: string | null;
accountId?: string | null;
}): boolean {
const targetAccountId = normalizeOptionalString(params.targetAccountId);
const accountId = normalizeOptionalString(params.accountId);
if (targetAccountId) {
return !accountId || normalizeAccountId(targetAccountId) === normalizeAccountId(accountId);
}
if (!accountId) {
return true;
}
const normalizedAccountId = normalizeAccountId(accountId);
const defaultAccountId = normalizeAccountId(resolveDefaultIMessageAccountId(params.cfg));
if (normalizedAccountId === defaultAccountId) {
return true;
}
const enabledAccountIds = listIMessageAccountIds(params.cfg)
.filter((candidateAccountId) =>
isIMessageApprovalTransportEnabled({
cfg: params.cfg,
accountId: candidateAccountId,
}),
)
.map((candidateAccountId) => normalizeAccountId(candidateAccountId));
return enabledAccountIds.length === 1 && enabledAccountIds[0] === normalizedAccountId;
}
function normalizeIMessageForwardTarget(
target: Pick<ChannelApprovalForwardTarget, "channel" | "to" | "accountId" | "threadId">,
): IMessageApprovalTarget | null {
@@ -120,73 +90,6 @@ function normalizeIMessageForwardTarget(
};
}
function hasMatchingIMessageTarget(params: {
cfg: OpenClawConfig;
config: ApprovalForwardingConfig;
accountId?: string | null;
target?: ChannelApprovalForwardTarget;
}): boolean {
const candidateTarget = params.target ? normalizeIMessageForwardTarget(params.target) : null;
return (params.config.targets ?? []).some((target) => {
const configuredTarget = normalizeIMessageForwardTarget(target);
if (!configuredTarget) {
return false;
}
if (
!targetAccountMatchesIMessageAccount({
cfg: params.cfg,
targetAccountId: configuredTarget.accountId,
accountId: params.accountId,
})
) {
return false;
}
if (!candidateTarget) {
return true;
}
return nativeApprovalTargetsMatch({
channel: "imessage",
left: configuredTarget,
right: candidateTarget,
});
});
}
function hasIMessageOriginOrSessionTarget(params: {
cfg: OpenClawConfig;
accountId?: string | null;
request: ApprovalRequest;
}): boolean {
if (resolveTurnSourceIMessageOriginTarget(params.request)) {
return true;
}
const sessionTarget = resolveApprovalRequestSessionTarget({
cfg: params.cfg,
request: params.request,
});
return (
normalizeLowercaseStringOrEmpty(sessionTarget?.channel) === "imessage" &&
targetAccountMatchesIMessageAccount({
cfg: params.cfg,
targetAccountId: sessionTarget?.accountId,
accountId: params.accountId,
})
);
}
const imessageApprovalForwarding = createChannelApprovalForwardingEvaluator({
channel: "imessage",
isTransportEnabled: isIMessageApprovalTransportEnabled,
hasMatchingTarget: hasMatchingIMessageTarget,
hasOriginOrSessionTarget: hasIMessageOriginOrSessionTarget,
});
const canApprovalPotentiallyRouteToIMessage = imessageApprovalForwarding.isPotentialRoute;
const canAnyApprovalPotentiallyRouteToIMessage = imessageApprovalForwarding.canAnyPotentiallyRoute;
const isIMessageSessionApprovalEligible = imessageApprovalForwarding.isSessionEligible;
const isIMessageExplicitTargetEligible = imessageApprovalForwarding.isExplicitTargetEligible;
function resolveTurnSourceIMessageOriginTarget(
request: ApprovalRequest,
): IMessageApprovalTarget | null {
@@ -212,6 +115,24 @@ function resolveSessionIMessageOriginTarget(sessionTarget: {
return to ? { to, accountId: normalizeOptionalString(sessionTarget.accountId) } : null;
}
const imessageApprovalRouteGates = createNativeApprovalChannelRouteGates({
channel: "imessage",
defaultForwardingMode: DEFAULT_APPROVAL_FORWARDING_MODE,
isTransportEnabled: isIMessageApprovalTransportEnabled,
listAccountIds: listIMessageAccountIds,
resolveDefaultAccountId: resolveDefaultIMessageAccountId,
normalizeForwardTarget: normalizeIMessageForwardTarget,
resolveTurnSourceTarget: resolveTurnSourceIMessageOriginTarget,
});
const {
canApprovalPotentiallyRouteToChannel: canApprovalPotentiallyRouteToIMessage,
canAnyApprovalPotentiallyRouteToChannel: canAnyApprovalPotentiallyRouteToIMessage,
isSessionApprovalEligible: isIMessageSessionApprovalEligible,
isExplicitTargetEligible: isIMessageExplicitTargetEligible,
shouldHandleApprovalRequest: shouldHandleIMessageApprovalRequest,
} = imessageApprovalRouteGates;
function resolveIMessageSessionTargetFromSessionKey(
sessionKey?: string | null,
): IMessageApprovalTarget | null {
@@ -308,15 +229,6 @@ export function shouldSuppressLocalIMessageExecApprovalPrompt(params: {
});
}
function shouldHandleIMessageApprovalRequest(params: {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind?: ApprovalKind;
request: ApprovalRequest;
}): boolean {
return imessageApprovalForwarding.shouldHandleRequest(params);
}
const resolveIMessageOriginTargetBase = createChannelNativeOriginTargetResolver({
channel: "imessage",
shouldHandleRequest: shouldHandleIMessageApprovalRequest,

View File

@@ -5,12 +5,10 @@ import {
import { createLazyChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
import type { ChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-runtime";
import {
createChannelApprovalForwardingEvaluator,
createChannelApproverDmTargetResolver,
createChannelNativeOriginTargetResolver,
createNativeApprovalChannelRouteGates,
createNativeApprovalForwardingFallbackSuppressor,
nativeApprovalTargetsMatch,
resolveApprovalRequestSessionTarget,
shouldSuppressLocalNativeExecApprovalPrompt,
} from "openclaw/plugin-sdk/approval-native-runtime";
import { buildApprovalReactionPendingContentForRequest } from "openclaw/plugin-sdk/approval-reaction-runtime";
@@ -24,7 +22,7 @@ import type {
} from "openclaw/plugin-sdk/channel-contract";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
import { normalizeAccountId, parseAgentSessionKey } from "openclaw/plugin-sdk/routing";
import { parseAgentSessionKey } from "openclaw/plugin-sdk/routing";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
@@ -40,6 +38,7 @@ import { normalizeSignalMessagingTarget } from "./normalize.js";
type ApprovalRequest = ExecApprovalRequest | PluginApprovalRequest;
type ApprovalKind = "exec" | "plugin";
type ApprovalForwardingConfig = NonNullable<NonNullable<OpenClawConfig["approvals"]>["exec"]>;
type ApprovalForwardingMode = NonNullable<ApprovalForwardingConfig["mode"]>;
type ChannelApprovalForwardTarget = Parameters<
NonNullable<
NonNullable<ChannelApprovalCapability["delivery"]>["shouldSuppressForwardingFallback"]
@@ -51,6 +50,8 @@ type SignalApprovalTarget = {
threadId?: string | number | null;
};
const DEFAULT_APPROVAL_FORWARDING_MODE: ApprovalForwardingMode = "session";
function isSignalApprovalTransportEnabled(params: {
cfg: OpenClawConfig;
accountId?: string | null;
@@ -58,35 +59,6 @@ function isSignalApprovalTransportEnabled(params: {
return resolveSignalAccount({ cfg: params.cfg, accountId: params.accountId }).enabled;
}
function targetAccountMatchesSignalAccount(params: {
cfg: OpenClawConfig;
targetAccountId?: string | null;
accountId?: string | null;
}): boolean {
const targetAccountId = normalizeOptionalString(params.targetAccountId);
const accountId = normalizeOptionalString(params.accountId);
if (targetAccountId) {
return !accountId || normalizeAccountId(targetAccountId) === normalizeAccountId(accountId);
}
if (!accountId) {
return true;
}
const normalizedAccountId = normalizeAccountId(accountId);
const defaultAccountId = normalizeAccountId(resolveDefaultSignalAccountId(params.cfg));
if (normalizedAccountId === defaultAccountId) {
return true;
}
const enabledAccountIds = listSignalAccountIds(params.cfg)
.filter((candidateAccountId) =>
isSignalApprovalTransportEnabled({
cfg: params.cfg,
accountId: candidateAccountId,
}),
)
.map((candidateAccountId) => normalizeAccountId(candidateAccountId));
return enabledAccountIds.length === 1 && enabledAccountIds[0] === normalizedAccountId;
}
function normalizeSignalForwardTarget(
target: Pick<ChannelApprovalForwardTarget, "channel" | "to" | "accountId" | "threadId">,
): SignalApprovalTarget | null {
@@ -104,82 +76,6 @@ function normalizeSignalForwardTarget(
};
}
function hasMatchingSignalTarget(params: {
cfg: OpenClawConfig;
config: ApprovalForwardingConfig;
accountId?: string | null;
target?: ChannelApprovalForwardTarget;
}): boolean {
const candidateTarget = params.target ? normalizeSignalForwardTarget(params.target) : null;
return (params.config.targets ?? []).some((target) => {
const configuredTarget = normalizeSignalForwardTarget(target);
if (!configuredTarget) {
return false;
}
if (
!targetAccountMatchesSignalAccount({
cfg: params.cfg,
targetAccountId: configuredTarget.accountId,
accountId: params.accountId,
})
) {
return false;
}
if (!candidateTarget) {
return true;
}
return nativeApprovalTargetsMatch({
channel: "signal",
left: configuredTarget,
right: candidateTarget,
});
});
}
function hasSignalOriginOrSessionTarget(params: {
cfg: OpenClawConfig;
accountId?: string | null;
request: ApprovalRequest;
}): boolean {
if (resolveTurnSourceSignalOriginTarget(params.request)) {
return true;
}
const sessionTarget = resolveApprovalRequestSessionTarget({
cfg: params.cfg,
request: params.request,
});
return (
normalizeLowercaseStringOrEmpty(sessionTarget?.channel) === "signal" &&
targetAccountMatchesSignalAccount({
cfg: params.cfg,
targetAccountId: sessionTarget?.accountId,
accountId: params.accountId,
})
);
}
const signalApprovalForwarding = createChannelApprovalForwardingEvaluator({
channel: "signal",
isTransportEnabled: isSignalApprovalTransportEnabled,
hasMatchingTarget: hasMatchingSignalTarget,
hasOriginOrSessionTarget: hasSignalOriginOrSessionTarget,
});
const canApprovalPotentiallyRouteToSignal = signalApprovalForwarding.isPotentialRoute;
const canAnyApprovalPotentiallyRouteToSignal = signalApprovalForwarding.canAnyPotentiallyRoute;
const isSignalSessionApprovalEligible = signalApprovalForwarding.isSessionEligible;
export function isSignalNativeApprovalHandlerConfigured(params: {
cfg: OpenClawConfig;
accountId?: string | null;
}): boolean {
return canAnyApprovalPotentiallyRouteToSignal({
...params,
nativeSessionOnly: true,
});
}
function resolveTurnSourceSignalOriginTarget(
request: ApprovalRequest,
): SignalApprovalTarget | null {
@@ -205,13 +101,29 @@ function resolveSessionSignalOriginTarget(sessionTarget: {
return to ? { to, accountId: normalizeOptionalString(sessionTarget.accountId) } : null;
}
function shouldHandleSignalApprovalRequest(params: {
const signalApprovalRouteGates = createNativeApprovalChannelRouteGates({
channel: "signal",
defaultForwardingMode: DEFAULT_APPROVAL_FORWARDING_MODE,
isTransportEnabled: isSignalApprovalTransportEnabled,
listAccountIds: listSignalAccountIds,
resolveDefaultAccountId: resolveDefaultSignalAccountId,
normalizeForwardTarget: normalizeSignalForwardTarget,
resolveTurnSourceTarget: resolveTurnSourceSignalOriginTarget,
});
const {
canApprovalPotentiallyRouteToChannel: canApprovalPotentiallyRouteToSignal,
canAnyApprovalPotentiallyRouteToChannel: canAnyApprovalPotentiallyRouteToSignal,
isNativeApprovalHandlerConfigured: isSignalNativeApprovalHandlerConfiguredBase,
isSessionApprovalEligible: isSignalSessionApprovalEligible,
shouldHandleApprovalRequest: shouldHandleSignalApprovalRequest,
} = signalApprovalRouteGates;
export function isSignalNativeApprovalHandlerConfigured(params: {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind?: ApprovalKind;
request: ApprovalRequest;
}): boolean {
return signalApprovalForwarding.shouldHandleRequest(params);
return isSignalNativeApprovalHandlerConfiguredBase(params);
}
function resolveSignalSessionTargetFromSessionKey(sessionKey?: string | null): string | null {

View File

@@ -5,12 +5,10 @@ import {
import { createLazyChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
import type { ChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-runtime";
import {
createChannelApprovalForwardingEvaluator,
createChannelApproverDmTargetResolver,
createChannelNativeOriginTargetResolver,
createNativeApprovalChannelRouteGates,
createNativeApprovalForwardingFallbackSuppressor,
nativeApprovalTargetsMatch,
resolveApprovalRequestSessionTarget,
} from "openclaw/plugin-sdk/approval-native-runtime";
import { buildApprovalReactionPromptPayloadForRequest } from "openclaw/plugin-sdk/approval-reaction-runtime";
import type {
@@ -19,7 +17,6 @@ import type {
} from "openclaw/plugin-sdk/approval-runtime";
import type { ChannelApprovalCapability } from "openclaw/plugin-sdk/channel-contract";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
import { normalizeAccountId } from "openclaw/plugin-sdk/routing";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
@@ -33,8 +30,8 @@ import { getWhatsAppApprovalApprovers, whatsappApprovalAuth } from "./approval-a
import { isWhatsAppGroupJid, normalizeWhatsAppMessagingTarget } from "./normalize.js";
type ApprovalRequest = ExecApprovalRequest | PluginApprovalRequest;
type ApprovalKind = "exec" | "plugin";
type ApprovalForwardingConfig = NonNullable<NonNullable<OpenClawConfig["approvals"]>["exec"]>;
type ApprovalForwardingMode = NonNullable<ApprovalForwardingConfig["mode"]>;
type ChannelApprovalForwardTarget = Parameters<
NonNullable<
NonNullable<ChannelApprovalCapability["delivery"]>["shouldSuppressForwardingFallback"]
@@ -46,6 +43,8 @@ type WhatsAppApprovalTarget = {
threadId?: string | number | null;
};
const DEFAULT_APPROVAL_FORWARDING_MODE: ApprovalForwardingMode = "session";
function isWhatsAppApprovalTransportEnabled(params: {
cfg: OpenClawConfig;
accountId?: string | null;
@@ -53,35 +52,6 @@ function isWhatsAppApprovalTransportEnabled(params: {
return resolveWhatsAppAccount({ cfg: params.cfg, accountId: params.accountId }).enabled;
}
function targetAccountMatchesWhatsAppAccount(params: {
cfg: OpenClawConfig;
targetAccountId?: string | null;
accountId?: string | null;
}): boolean {
const targetAccountId = normalizeOptionalString(params.targetAccountId);
const accountId = normalizeOptionalString(params.accountId);
if (targetAccountId) {
return !accountId || normalizeAccountId(targetAccountId) === normalizeAccountId(accountId);
}
if (!accountId) {
return true;
}
const normalizedAccountId = normalizeAccountId(accountId);
const defaultAccountId = normalizeAccountId(resolveDefaultWhatsAppAccountId(params.cfg));
if (normalizedAccountId === defaultAccountId) {
return true;
}
const enabledAccountIds = listWhatsAppAccountIds(params.cfg)
.filter((candidateAccountId) =>
isWhatsAppApprovalTransportEnabled({
cfg: params.cfg,
accountId: candidateAccountId,
}),
)
.map((candidateAccountId) => normalizeAccountId(candidateAccountId));
return enabledAccountIds.length === 1 && enabledAccountIds[0] === normalizedAccountId;
}
function normalizeWhatsAppForwardTarget(
target: Pick<ChannelApprovalForwardTarget, "channel" | "to" | "accountId" | "threadId">,
): WhatsAppApprovalTarget | null {
@@ -99,73 +69,6 @@ function normalizeWhatsAppForwardTarget(
};
}
function hasMatchingWhatsAppTarget(params: {
cfg: OpenClawConfig;
config: ApprovalForwardingConfig;
accountId?: string | null;
target?: ChannelApprovalForwardTarget;
}): boolean {
const candidateTarget = params.target ? normalizeWhatsAppForwardTarget(params.target) : null;
return (params.config.targets ?? []).some((target) => {
const configuredTarget = normalizeWhatsAppForwardTarget(target);
if (!configuredTarget) {
return false;
}
if (
!targetAccountMatchesWhatsAppAccount({
cfg: params.cfg,
targetAccountId: configuredTarget.accountId,
accountId: params.accountId,
})
) {
return false;
}
if (!candidateTarget) {
return true;
}
return nativeApprovalTargetsMatch({
channel: "whatsapp",
left: configuredTarget,
right: candidateTarget,
});
});
}
function hasWhatsAppOriginOrSessionTarget(params: {
cfg: OpenClawConfig;
accountId?: string | null;
request: ApprovalRequest;
}): boolean {
if (resolveTurnSourceWhatsAppOriginTarget(params.request)) {
return true;
}
const sessionTarget = resolveApprovalRequestSessionTarget({
cfg: params.cfg,
request: params.request,
});
return (
normalizeLowercaseStringOrEmpty(sessionTarget?.channel) === "whatsapp" &&
targetAccountMatchesWhatsAppAccount({
cfg: params.cfg,
targetAccountId: sessionTarget?.accountId,
accountId: params.accountId,
})
);
}
const whatsappApprovalForwarding = createChannelApprovalForwardingEvaluator({
channel: "whatsapp",
isTransportEnabled: isWhatsAppApprovalTransportEnabled,
hasMatchingTarget: hasMatchingWhatsAppTarget,
hasOriginOrSessionTarget: hasWhatsAppOriginOrSessionTarget,
});
const canApprovalPotentiallyRouteToWhatsApp = whatsappApprovalForwarding.isPotentialRoute;
const canAnyApprovalPotentiallyRouteToWhatsApp = whatsappApprovalForwarding.canAnyPotentiallyRoute;
const isWhatsAppSessionApprovalEligible = whatsappApprovalForwarding.isSessionEligible;
const isWhatsAppExplicitTargetEligible = whatsappApprovalForwarding.isExplicitTargetEligible;
function resolveTurnSourceWhatsAppOriginTarget(
request: ApprovalRequest,
): WhatsAppApprovalTarget | null {
@@ -191,14 +94,23 @@ function resolveSessionWhatsAppOriginTarget(sessionTarget: {
return to ? { to, accountId: normalizeOptionalString(sessionTarget.accountId) } : null;
}
function shouldHandleWhatsAppApprovalRequest(params: {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind?: ApprovalKind;
request: ApprovalRequest;
}): boolean {
return whatsappApprovalForwarding.shouldHandleRequest(params);
}
const whatsappApprovalRouteGates = createNativeApprovalChannelRouteGates({
channel: "whatsapp",
defaultForwardingMode: DEFAULT_APPROVAL_FORWARDING_MODE,
isTransportEnabled: isWhatsAppApprovalTransportEnabled,
listAccountIds: listWhatsAppAccountIds,
resolveDefaultAccountId: resolveDefaultWhatsAppAccountId,
normalizeForwardTarget: normalizeWhatsAppForwardTarget,
resolveTurnSourceTarget: resolveTurnSourceWhatsAppOriginTarget,
});
const {
canApprovalPotentiallyRouteToChannel: canApprovalPotentiallyRouteToWhatsApp,
canAnyApprovalPotentiallyRouteToChannel: canAnyApprovalPotentiallyRouteToWhatsApp,
isSessionApprovalEligible: isWhatsAppSessionApprovalEligible,
isExplicitTargetEligible: isWhatsAppExplicitTargetEligible,
shouldHandleApprovalRequest: shouldHandleWhatsAppApprovalRequest,
} = whatsappApprovalRouteGates;
const resolveWhatsAppOriginTargetBase = createChannelNativeOriginTargetResolver({
channel: "whatsapp",

View File

@@ -42,19 +42,7 @@ const STRICT_LITERAL_STRUCTS = new Set([
const DEFAULTED_OPTIONAL_INIT_PARAMS: Record<string, Set<string>> = {
SessionsAbortParams: new Set(["agentId"]),
SessionOperationEvent: new Set(["agentId"]),
SessionsUsageParams: new Set(["agentId", "agentScope"]),
SessionsCompactionListParams: new Set(["agentId"]),
SessionsCompactionGetParams: new Set(["agentId"]),
SessionsCompactionBranchParams: new Set(["agentId"]),
SessionsCompactionRestoreParams: new Set(["agentId"]),
SessionsSendParams: new Set(["agentId"]),
SessionsMessagesSubscribeParams: new Set(["agentId"]),
SessionsMessagesUnsubscribeParams: new Set(["agentId"]),
SessionsPatchParams: new Set(["agentId"]),
SessionsResetParams: new Set(["agentId"]),
SessionsDeleteParams: new Set(["agentId"]),
SessionsCompactParams: new Set(["agentId"]),
ArtifactsListParams: new Set(["agentId"]),
ArtifactsGetParams: new Set(["agentId"]),
ArtifactsDownloadParams: new Set(["agentId"]),

View File

@@ -927,6 +927,7 @@ export function resetEmbeddedAttemptHarness(
}
hoisted.createAgentSessionMock.mockReset();
hoisted.sessionManagerOpenMock.mockReset().mockReturnValue(hoisted.sessionManager);
hoisted.defaultResourceLoaderInitMock.mockReset();
hoisted.resolveSandboxContextMock.mockReset();
hoisted.ensureGlobalUndiciEnvProxyDispatcherMock.mockReset();
hoisted.ensureGlobalUndiciDispatcherStreamTimeoutsMock.mockReset();

View File

@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
import {
createChannelApproverDmTargetResolver,
createChannelNativeOriginTargetResolver,
createNativeApprovalChannelRouteGates,
createNativeApprovalForwardingFallbackSuppressor,
type NativeApprovalTarget,
nativeApprovalTargetsMatch,
@@ -15,6 +16,215 @@ const EMPTY_SESSION_CFG = {
},
} satisfies OpenClawConfig;
function createMatrixRouteGates(options?: {
enabledAccounts?: readonly string[];
accountIds?: readonly string[];
defaultAccountId?: string;
}) {
const enabledAccounts = new Set(options?.enabledAccounts ?? ["default"]);
return createNativeApprovalChannelRouteGates<NativeApprovalTarget>({
channel: "matrix",
defaultForwardingMode: "session",
isTransportEnabled: ({ accountId }) => enabledAccounts.has(accountId ?? "default"),
listAccountIds: () => options?.accountIds ?? ["default"],
resolveDefaultAccountId: () => options?.defaultAccountId ?? "default",
normalizeForwardTarget: (target) =>
target.channel === "matrix"
? {
to: target.to,
accountId: target.accountId ?? undefined,
threadId: target.threadId ?? undefined,
}
: null,
resolveTurnSourceTarget: (request) =>
request.request.turnSourceChannel === "matrix" && request.request.turnSourceTo
? {
to: request.request.turnSourceTo,
accountId: request.request.turnSourceAccountId ?? undefined,
threadId: request.request.turnSourceThreadId ?? undefined,
}
: null,
});
}
const matrixExecRequest = {
id: "req-1",
request: {
agentId: "agent-a",
command: "echo hi",
sessionKey: "agent:agent-a:matrix:room-1",
turnSourceAccountId: "default",
turnSourceChannel: "matrix",
turnSourceTo: "room-1",
},
createdAtMs: 0,
expiresAtMs: 1000,
} as const;
const matrixPluginRequest = {
id: "plugin:req-1",
request: {
agentId: "agent-a",
description: "Allow access",
sessionKey: "agent:agent-a:matrix:room-1",
title: "Plugin approval",
turnSourceAccountId: "default",
turnSourceChannel: "matrix",
turnSourceTo: "room-1",
},
createdAtMs: 0,
expiresAtMs: 1000,
} as const;
describe("createNativeApprovalChannelRouteGates", () => {
it("separates session-native and explicit target routing by approval family", () => {
const gates = createMatrixRouteGates();
const cfg = {
approvals: {
exec: {
enabled: true,
mode: "targets",
targets: [{ channel: "matrix", to: "room-1" }],
},
plugin: {
enabled: true,
mode: "session",
},
},
} satisfies OpenClawConfig;
expect(
gates.canApprovalPotentiallyRouteToChannel({
cfg,
approvalKind: "exec",
}),
).toBe(true);
expect(
gates.canApprovalPotentiallyRouteToChannel({
cfg,
approvalKind: "exec",
nativeSessionOnly: true,
}),
).toBe(false);
expect(
gates.canApprovalPotentiallyRouteToChannel({
cfg,
approvalKind: "plugin",
nativeSessionOnly: true,
}),
).toBe(true);
expect(gates.isNativeApprovalHandlerConfigured({ cfg })).toBe(true);
expect(
gates.shouldHandleApprovalRequest({
cfg,
request: matrixExecRequest,
}),
).toBe(false);
expect(
gates.shouldHandleApprovalRequest({
cfg,
request: matrixPluginRequest,
}),
).toBe(true);
expect(
gates.isExplicitTargetEligible({
cfg,
approvalKind: "exec",
request: matrixExecRequest,
target: { channel: "matrix", to: "room-1", source: "target" },
}),
).toBe(true);
});
it("applies forwarding filters before accepting a session route", () => {
const gates = createMatrixRouteGates();
const cfg = {
approvals: {
exec: {
enabled: true,
agentFilter: ["agent-a"],
sessionFilter: ["matrix:room"],
},
},
} satisfies OpenClawConfig;
expect(
gates.isSessionApprovalEligible({
cfg,
approvalKind: "exec",
request: matrixExecRequest,
}),
).toBe(true);
expect(
gates.isSessionApprovalEligible({
cfg,
approvalKind: "exec",
request: {
...matrixExecRequest,
request: {
...matrixExecRequest.request,
agentId: "agent-b",
sessionKey: "agent:agent-b:matrix:room-1",
},
},
}),
).toBe(false);
});
it("uses default and single-enabled account fallback for unscoped targets", () => {
const cfg = {
approvals: {
exec: {
enabled: true,
mode: "targets",
targets: [{ channel: "matrix", to: "room-1" }],
},
},
} satisfies OpenClawConfig;
const target = { channel: "matrix", to: "room-1", source: "target" } as const;
expect(
createMatrixRouteGates({
accountIds: ["default", "work"],
enabledAccounts: ["default", "work"],
}).isExplicitTargetEligible({
cfg,
accountId: "default",
approvalKind: "exec",
request: matrixExecRequest,
target,
}),
).toBe(true);
expect(
createMatrixRouteGates({
accountIds: ["default", "work"],
enabledAccounts: ["work"],
}).isExplicitTargetEligible({
cfg,
accountId: "work",
approvalKind: "exec",
request: matrixExecRequest,
target,
}),
).toBe(true);
expect(
createMatrixRouteGates({
accountIds: ["default", "work"],
enabledAccounts: ["default", "work"],
}).isExplicitTargetEligible({
cfg,
accountId: "work",
approvalKind: "exec",
request: matrixExecRequest,
target,
}),
).toBe(false);
});
});
describe("createChannelNativeOriginTargetResolver", () => {
it("reuses shared turn-source routing and respects shouldHandle gating", () => {
const resolveOriginTarget = createChannelNativeOriginTargetResolver<NativeApprovalTarget>({

View File

@@ -9,9 +9,17 @@ import {
type ExecApprovalReplyMetadata,
} from "../infra/exec-approval-reply.js";
import type { ExecApprovalSessionTarget } from "../infra/exec-approval-session-target.js";
import { resolveApprovalRequestOriginTarget } from "../infra/exec-approval-session-target.js";
import {
resolveApprovalRequestOriginTarget,
resolveApprovalRequestSessionTarget,
} from "../infra/exec-approval-session-target.js";
import type { ExecApprovalRequest } from "../infra/exec-approvals.js";
import type { PluginApprovalRequest } from "../infra/plugin-approvals.js";
import { normalizeAccountId } from "../routing/session-key.js";
import {
normalizeLowercaseStringOrEmpty,
normalizeOptionalString,
} from "../shared/string-coerce.js";
import type { ChannelApprovalCapability, ChannelOutboundPayloadHint } from "./channel-contract.js";
import { channelRouteTargetsMatchExact } from "./channel-route.js";
import type { OpenClawConfig } from "./config-runtime.js";
@@ -127,6 +135,54 @@ type NativeApprovalForwardingFallbackSuppressorParams<TTarget extends NativeAppr
targetsMatch?: (left: TTarget, right: TTarget) => boolean;
};
type NativeApprovalChannelRouteGateParams<TTarget extends NativeApprovalTarget> = {
channel: string;
defaultForwardingMode: ExecApprovalForwardingMode;
isTransportEnabled: (params: { cfg: OpenClawConfig; accountId?: string | null }) => boolean;
listAccountIds: (cfg: OpenClawConfig) => readonly string[];
resolveDefaultAccountId: (cfg: OpenClawConfig) => string;
normalizeForwardTarget: (target: NativeApprovalForwardTarget) => TTarget | null;
resolveTurnSourceTarget: (request: ApprovalRequest) => TTarget | null;
targetsMatch?: (left: TTarget, right: TTarget) => boolean;
};
type NativeApprovalChannelRouteGates = {
canApprovalPotentiallyRouteToChannel: (params: {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind: ApprovalKind;
nativeSessionOnly?: boolean;
}) => boolean;
canAnyApprovalPotentiallyRouteToChannel: (params: {
cfg: OpenClawConfig;
accountId?: string | null;
nativeSessionOnly?: boolean;
}) => boolean;
isNativeApprovalHandlerConfigured: (params: {
cfg: OpenClawConfig;
accountId?: string | null;
}) => boolean;
isSessionApprovalEligible: (params: {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind: ApprovalKind;
request: ApprovalRequest;
}) => boolean;
isExplicitTargetEligible: (params: {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind: ApprovalKind;
request: ApprovalRequest;
target: NativeApprovalForwardTarget;
}) => boolean;
shouldHandleApprovalRequest: (params: {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind?: ApprovalKind;
request: ApprovalRequest;
}) => boolean;
};
type NativeOriginResolverParams<TTarget extends NativeApprovalTarget> = {
channel: string;
shouldHandleRequest?: (params: ApprovalResolverParams) => boolean;
@@ -435,6 +491,242 @@ export function createChannelApprovalForwardingEvaluator(
};
}
function normalizeApprovalForwardingModeWithDefault(params: {
config: ExecApprovalForwardingConfig;
defaultForwardingMode: ExecApprovalForwardingMode;
}): ExecApprovalForwardingMode {
return params.config.mode ?? params.defaultForwardingMode;
}
export function createNativeApprovalChannelRouteGates<TTarget extends NativeApprovalTarget>(
params: NativeApprovalChannelRouteGateParams<TTarget>,
): NativeApprovalChannelRouteGates {
const targetsMatch =
params.targetsMatch ??
((left: TTarget, right: TTarget) =>
nativeApprovalTargetsMatch({ channel: params.channel, left, right }));
const targetAccountMatchesChannelAccount = (input: {
cfg: OpenClawConfig;
targetAccountId?: string | null;
accountId?: string | null;
}): boolean => {
const targetAccountId = normalizeOptionalString(input.targetAccountId);
const accountId = normalizeOptionalString(input.accountId);
if (targetAccountId) {
return !accountId || normalizeAccountId(targetAccountId) === normalizeAccountId(accountId);
}
if (!accountId) {
return true;
}
const normalizedAccountId = normalizeAccountId(accountId);
const defaultAccountId = normalizeAccountId(params.resolveDefaultAccountId(input.cfg));
if (normalizedAccountId === defaultAccountId) {
return true;
}
const enabledAccountIds = params
.listAccountIds(input.cfg)
.filter((candidateAccountId) =>
params.isTransportEnabled({
cfg: input.cfg,
accountId: candidateAccountId,
}),
)
.map((candidateAccountId) => normalizeAccountId(candidateAccountId));
return enabledAccountIds.length === 1 && enabledAccountIds[0] === normalizedAccountId;
};
const hasMatchingChannelTarget = (input: {
cfg: OpenClawConfig;
config: ExecApprovalForwardingConfig;
accountId?: string | null;
target?: NativeApprovalForwardTarget;
}): boolean => {
const candidateTarget = input.target ? params.normalizeForwardTarget(input.target) : null;
return (input.config.targets ?? []).some((target) => {
const configuredTarget = params.normalizeForwardTarget(target);
if (!configuredTarget) {
return false;
}
if (
!targetAccountMatchesChannelAccount({
cfg: input.cfg,
targetAccountId: configuredTarget.accountId,
accountId: input.accountId,
})
) {
return false;
}
if (!candidateTarget) {
return true;
}
return targetsMatch(configuredTarget, candidateTarget);
});
};
const hasChannelOriginOrSessionTarget = (input: {
cfg: OpenClawConfig;
accountId?: string | null;
request: ApprovalRequest;
}): boolean => {
if (params.resolveTurnSourceTarget(input.request)) {
return true;
}
const sessionTarget = resolveApprovalRequestSessionTarget({
cfg: input.cfg,
request: input.request,
});
return (
normalizeLowercaseStringOrEmpty(sessionTarget?.channel) === params.channel &&
targetAccountMatchesChannelAccount({
cfg: input.cfg,
targetAccountId: sessionTarget?.accountId,
accountId: input.accountId,
})
);
};
const canApprovalPotentiallyRouteToChannel = (input: {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind: ApprovalKind;
nativeSessionOnly?: boolean;
}): boolean => {
if (!params.isTransportEnabled(input)) {
return false;
}
const config = resolveApprovalForwardingConfig(input);
if (!config?.enabled) {
return false;
}
const mode = normalizeApprovalForwardingModeWithDefault({
config,
defaultForwardingMode: params.defaultForwardingMode,
});
if (approvalModeIncludesSession(mode)) {
return true;
}
if (input.nativeSessionOnly) {
return false;
}
return (
approvalModeIncludesTargets(mode) &&
hasMatchingChannelTarget({
cfg: input.cfg,
config,
accountId: input.accountId,
})
);
};
const canAnyApprovalPotentiallyRouteToChannel = (input: {
cfg: OpenClawConfig;
accountId?: string | null;
nativeSessionOnly?: boolean;
}): boolean =>
canApprovalPotentiallyRouteToChannel({
...input,
approvalKind: "exec",
}) ||
canApprovalPotentiallyRouteToChannel({
...input,
approvalKind: "plugin",
});
const isSessionApprovalEligible = (input: {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind: ApprovalKind;
request: ApprovalRequest;
}): boolean => {
if (!params.isTransportEnabled(input)) {
return false;
}
const config = resolveApprovalForwardingConfig(input);
if (!config?.enabled) {
return false;
}
const mode = normalizeApprovalForwardingModeWithDefault({
config,
defaultForwardingMode: params.defaultForwardingMode,
});
if (!approvalModeIncludesSession(mode)) {
return false;
}
if (!matchesForwardingFilters({ config, request: input.request })) {
return false;
}
if (
!doesApprovalRequestMatchChannelAccount({
cfg: input.cfg,
request: input.request,
channel: params.channel,
accountId: input.accountId,
})
) {
return false;
}
return hasChannelOriginOrSessionTarget(input);
};
const isExplicitTargetEligible = (input: {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind: ApprovalKind;
request: ApprovalRequest;
target: NativeApprovalForwardTarget;
}): boolean => {
if (!params.isTransportEnabled(input)) {
return false;
}
const config = resolveApprovalForwardingConfig(input);
if (!config?.enabled) {
return false;
}
const mode = normalizeApprovalForwardingModeWithDefault({
config,
defaultForwardingMode: params.defaultForwardingMode,
});
if (!approvalModeIncludesTargets(mode)) {
return false;
}
if (!matchesForwardingFilters({ config, request: input.request })) {
return false;
}
return hasMatchingChannelTarget({
cfg: input.cfg,
config,
accountId: input.accountId,
target: input.target,
});
};
const shouldHandleApprovalRequest = (input: {
cfg: OpenClawConfig;
accountId?: string | null;
approvalKind?: ApprovalKind;
request: ApprovalRequest;
}): boolean =>
isSessionApprovalEligible({
...input,
approvalKind: resolveApprovalKind(input.request, input.approvalKind),
});
return {
canApprovalPotentiallyRouteToChannel,
canAnyApprovalPotentiallyRouteToChannel,
isNativeApprovalHandlerConfigured: (input) =>
canAnyApprovalPotentiallyRouteToChannel({
...input,
nativeSessionOnly: true,
}),
isSessionApprovalEligible,
isExplicitTargetEligible,
shouldHandleApprovalRequest,
};
}
function normalizeOptionalAccountId(value?: string | null): string | undefined {
return value?.trim() || undefined;
}

View File

@@ -2,6 +2,7 @@ export {
createChannelApprovalForwardingEvaluator,
createChannelApproverDmTargetResolver,
createChannelNativeOriginTargetResolver,
createNativeApprovalChannelRouteGates,
createNativeApprovalForwardingFallbackSuppressor,
nativeApprovalTargetsMatch,
resolveApprovalKind,

View File

@@ -1,11 +1,11 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-29T18:10:07.578Z",
"generatedAt": "2026-05-29T21:01:05.561Z",
"locale": "ar",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "1ddd9306566b37255394133ec48e05dfe9dd502fd57a34d3c9c96152331730e1",
"totalKeys": 1269,
"translatedKeys": 1269,
"sourceHash": "d51c5347e718c0fbe7353fccf82cefc0ef09b95b25acbc840a2f19b0c17b9296",
"totalKeys": 1271,
"translatedKeys": 1271,
"workflow": 1
}

2
ui/src/i18n/.i18n/ar.tm.jsonl generated Normal file
View File

@@ -0,0 +1,2 @@
{"cache_key":"31baf5b9e4f804c6e3a61e02532c991cbb3d27c24b36ad51b9505cdc8bdf9f97","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goalNote","source_path":"ui/src/i18n/locales/ar.ts","src_lang":"en","text":"Goal note","text_hash":"1afb7855a394ef7078728de1c804d6b995413db4eafe7d74190076cb9ed2c9f5","tgt_lang":"ar","translated":"ملاحظة الهدف","updated_at":"2026-05-29T21:01:05.556Z"}
{"cache_key":"b83a08557f604af51d1d904a15fd37755f3e3f311e32b5dff3c7218cfd75183d","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goal","source_path":"ui/src/i18n/locales/ar.ts","src_lang":"en","text":"Goal","text_hash":"cdbf6975e8a35b0d03558be6822dfae166482c24fb86b0433f60e8167f5c91e4","tgt_lang":"ar","translated":"الهدف","updated_at":"2026-05-29T21:01:05.553Z"}

View File

@@ -1,11 +1,11 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-29T18:09:57.996Z",
"generatedAt": "2026-05-29T21:00:05.360Z",
"locale": "de",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "1ddd9306566b37255394133ec48e05dfe9dd502fd57a34d3c9c96152331730e1",
"totalKeys": 1269,
"translatedKeys": 1269,
"sourceHash": "d51c5347e718c0fbe7353fccf82cefc0ef09b95b25acbc840a2f19b0c17b9296",
"totalKeys": 1271,
"translatedKeys": 1271,
"workflow": 1
}

2
ui/src/i18n/.i18n/de.tm.jsonl generated Normal file
View File

@@ -0,0 +1,2 @@
{"cache_key":"3291b27d87c9b085d83645bf3d093be323f1809505f54b9edf8bd2691601fde6","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goal","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"Goal","text_hash":"cdbf6975e8a35b0d03558be6822dfae166482c24fb86b0433f60e8167f5c91e4","tgt_lang":"de","translated":"Ziel","updated_at":"2026-05-29T21:00:05.331Z"}
{"cache_key":"e50aa42b44fa61de5322d01701b08a1460b315ebf5beb258a7b68d1c40200a02","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goalNote","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"Goal note","text_hash":"1afb7855a394ef7078728de1c804d6b995413db4eafe7d74190076cb9ed2c9f5","tgt_lang":"de","translated":"Zielnotiz","updated_at":"2026-05-29T21:00:05.331Z"}

View File

@@ -1,11 +1,11 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-29T18:10:00.520Z",
"generatedAt": "2026-05-29T21:00:16.244Z",
"locale": "es",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "1ddd9306566b37255394133ec48e05dfe9dd502fd57a34d3c9c96152331730e1",
"totalKeys": 1269,
"translatedKeys": 1269,
"sourceHash": "d51c5347e718c0fbe7353fccf82cefc0ef09b95b25acbc840a2f19b0c17b9296",
"totalKeys": 1271,
"translatedKeys": 1271,
"workflow": 1
}

2
ui/src/i18n/.i18n/es.tm.jsonl generated Normal file
View File

@@ -0,0 +1,2 @@
{"cache_key":"9d69982042ae98028cd211bab9a9b5f33b1ddaa2a7ee8231a6540e9e63302e25","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goalNote","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"Goal note","text_hash":"1afb7855a394ef7078728de1c804d6b995413db4eafe7d74190076cb9ed2c9f5","tgt_lang":"es","translated":"Nota del objetivo","updated_at":"2026-05-29T21:00:16.236Z"}
{"cache_key":"b626995f71c58bfb263806f12b5eece5091a529999f3f1bfccbc5df135f6a178","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goal","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"Goal","text_hash":"cdbf6975e8a35b0d03558be6822dfae166482c24fb86b0433f60e8167f5c91e4","tgt_lang":"es","translated":"Objetivo","updated_at":"2026-05-29T21:00:16.236Z"}

View File

@@ -1,11 +1,11 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-29T18:10:21.090Z",
"generatedAt": "2026-05-29T21:02:42.653Z",
"locale": "fa",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "1ddd9306566b37255394133ec48e05dfe9dd502fd57a34d3c9c96152331730e1",
"totalKeys": 1269,
"translatedKeys": 1269,
"sourceHash": "d51c5347e718c0fbe7353fccf82cefc0ef09b95b25acbc840a2f19b0c17b9296",
"totalKeys": 1271,
"translatedKeys": 1271,
"workflow": 1
}

2
ui/src/i18n/.i18n/fa.tm.jsonl generated Normal file
View File

@@ -0,0 +1,2 @@
{"cache_key":"4a2cd58d849e04841d7cc035ec2c90d722c4fed30f648da173044275addefb73","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goalNote","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"Goal note","text_hash":"1afb7855a394ef7078728de1c804d6b995413db4eafe7d74190076cb9ed2c9f5","tgt_lang":"fa","translated":"یادداشت هدف","updated_at":"2026-05-29T21:02:42.628Z"}
{"cache_key":"71d09491009ef87d3af376ce4162e9b18d97162c0d76ba6553a489cc3cd638c6","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goal","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"Goal","text_hash":"cdbf6975e8a35b0d03558be6822dfae166482c24fb86b0433f60e8167f5c91e4","tgt_lang":"fa","translated":"هدف","updated_at":"2026-05-29T21:02:42.628Z"}

View File

@@ -1,11 +1,11 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-29T18:10:06.134Z",
"generatedAt": "2026-05-29T21:00:46.756Z",
"locale": "fr",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "1ddd9306566b37255394133ec48e05dfe9dd502fd57a34d3c9c96152331730e1",
"totalKeys": 1269,
"translatedKeys": 1269,
"sourceHash": "d51c5347e718c0fbe7353fccf82cefc0ef09b95b25acbc840a2f19b0c17b9296",
"totalKeys": 1271,
"translatedKeys": 1271,
"workflow": 1
}

2
ui/src/i18n/.i18n/fr.tm.jsonl generated Normal file
View File

@@ -0,0 +1,2 @@
{"cache_key":"a87418ac5fda24f3add83f591aa5c4b8ebd1d3914f11b428e8eac9cc3b959fc5","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goalNote","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"Goal note","text_hash":"1afb7855a394ef7078728de1c804d6b995413db4eafe7d74190076cb9ed2c9f5","tgt_lang":"fr","translated":"Note dobjectif","updated_at":"2026-05-29T21:00:46.685Z"}
{"cache_key":"d0488f998b840872ae71bc8d8209e714ef16aa4216a6b422006b4762744e1289","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goal","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"Goal","text_hash":"cdbf6975e8a35b0d03558be6822dfae166482c24fb86b0433f60e8167f5c91e4","tgt_lang":"fr","translated":"Objectif","updated_at":"2026-05-29T21:00:46.685Z"}

View File

@@ -1,11 +1,11 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-29T18:10:13.111Z",
"generatedAt": "2026-05-29T21:01:44.176Z",
"locale": "id",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "1ddd9306566b37255394133ec48e05dfe9dd502fd57a34d3c9c96152331730e1",
"totalKeys": 1269,
"translatedKeys": 1269,
"sourceHash": "d51c5347e718c0fbe7353fccf82cefc0ef09b95b25acbc840a2f19b0c17b9296",
"totalKeys": 1271,
"translatedKeys": 1271,
"workflow": 1
}

2
ui/src/i18n/.i18n/id.tm.jsonl generated Normal file
View File

@@ -0,0 +1,2 @@
{"cache_key":"2b375a43961fdfcfdd974503b29314f06a33027760f2592eace136fda36732fd","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goalNote","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"Goal note","text_hash":"1afb7855a394ef7078728de1c804d6b995413db4eafe7d74190076cb9ed2c9f5","tgt_lang":"id","translated":"Catatan tujuan","updated_at":"2026-05-29T21:01:43.534Z"}
{"cache_key":"7781d543931e105b5b122201b4f1913e29c028cc7b6e0695c5a91ec2d84dce0c","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goal","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"Goal","text_hash":"cdbf6975e8a35b0d03558be6822dfae166482c24fb86b0433f60e8167f5c91e4","tgt_lang":"id","translated":"Tujuan","updated_at":"2026-05-29T21:01:43.511Z"}

View File

@@ -1,11 +1,11 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-29T18:10:08.751Z",
"generatedAt": "2026-05-29T21:01:19.882Z",
"locale": "it",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "1ddd9306566b37255394133ec48e05dfe9dd502fd57a34d3c9c96152331730e1",
"totalKeys": 1269,
"translatedKeys": 1269,
"sourceHash": "d51c5347e718c0fbe7353fccf82cefc0ef09b95b25acbc840a2f19b0c17b9296",
"totalKeys": 1271,
"translatedKeys": 1271,
"workflow": 1
}

2
ui/src/i18n/.i18n/it.tm.jsonl generated Normal file
View File

@@ -0,0 +1,2 @@
{"cache_key":"10f9390d7ed31c5188b9663412768c8d085c4311c703765a09cee4309636e530","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goalNote","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"Goal note","text_hash":"1afb7855a394ef7078728de1c804d6b995413db4eafe7d74190076cb9ed2c9f5","tgt_lang":"it","translated":"Nota sull'obiettivo","updated_at":"2026-05-29T21:01:19.873Z"}
{"cache_key":"31e985892e47fba1b0d3c69810e7c70676c475936ca1382069c2ccc3c82a6a85","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goal","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"Goal","text_hash":"cdbf6975e8a35b0d03558be6822dfae166482c24fb86b0433f60e8167f5c91e4","tgt_lang":"it","translated":"Obiettivo","updated_at":"2026-05-29T21:01:19.873Z"}

View File

@@ -1,11 +1,11 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-29T18:10:01.779Z",
"generatedAt": "2026-05-29T21:00:25.952Z",
"locale": "ja-JP",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "1ddd9306566b37255394133ec48e05dfe9dd502fd57a34d3c9c96152331730e1",
"totalKeys": 1269,
"translatedKeys": 1269,
"sourceHash": "d51c5347e718c0fbe7353fccf82cefc0ef09b95b25acbc840a2f19b0c17b9296",
"totalKeys": 1271,
"translatedKeys": 1271,
"workflow": 1
}

2
ui/src/i18n/.i18n/ja-JP.tm.jsonl generated Normal file
View File

@@ -0,0 +1,2 @@
{"cache_key":"385cee2f0cb4a83b0e53424167bef43a488ba00da62ea56753a393ec6b6960b2","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goal","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"Goal","text_hash":"cdbf6975e8a35b0d03558be6822dfae166482c24fb86b0433f60e8167f5c91e4","tgt_lang":"ja-JP","translated":"目標","updated_at":"2026-05-29T21:00:25.945Z"}
{"cache_key":"d18570791fe4a33cac91265fb875e2e15cb4c0172d17e4ba14bd8208a1df3401","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goalNote","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"Goal note","text_hash":"1afb7855a394ef7078728de1c804d6b995413db4eafe7d74190076cb9ed2c9f5","tgt_lang":"ja-JP","translated":"目標メモ","updated_at":"2026-05-29T21:00:25.945Z"}

View File

@@ -1,11 +1,11 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-29T18:10:03.892Z",
"generatedAt": "2026-05-29T21:00:36.567Z",
"locale": "ko",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "1ddd9306566b37255394133ec48e05dfe9dd502fd57a34d3c9c96152331730e1",
"totalKeys": 1269,
"translatedKeys": 1269,
"sourceHash": "d51c5347e718c0fbe7353fccf82cefc0ef09b95b25acbc840a2f19b0c17b9296",
"totalKeys": 1271,
"translatedKeys": 1271,
"workflow": 1
}

2
ui/src/i18n/.i18n/ko.tm.jsonl generated Normal file
View File

@@ -0,0 +1,2 @@
{"cache_key":"6661a8580f033e530995fca7fa9a21141d4c43f499c3bac4b5bcde8e4f085a50","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goalNote","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"Goal note","text_hash":"1afb7855a394ef7078728de1c804d6b995413db4eafe7d74190076cb9ed2c9f5","tgt_lang":"ko","translated":"목표 메모","updated_at":"2026-05-29T21:00:36.520Z"}
{"cache_key":"c4aa03b43d324f829c9cea82c3296e21c9b65f7388dba7979b5dd57318a5f957","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goal","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"Goal","text_hash":"cdbf6975e8a35b0d03558be6822dfae166482c24fb86b0433f60e8167f5c91e4","tgt_lang":"ko","translated":"목표","updated_at":"2026-05-29T21:00:36.520Z"}

View File

@@ -1,11 +1,11 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-29T18:10:19.671Z",
"generatedAt": "2026-05-29T21:02:33.330Z",
"locale": "nl",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "1ddd9306566b37255394133ec48e05dfe9dd502fd57a34d3c9c96152331730e1",
"totalKeys": 1269,
"translatedKeys": 1269,
"sourceHash": "d51c5347e718c0fbe7353fccf82cefc0ef09b95b25acbc840a2f19b0c17b9296",
"totalKeys": 1271,
"translatedKeys": 1271,
"workflow": 1
}

2
ui/src/i18n/.i18n/nl.tm.jsonl generated Normal file
View File

@@ -0,0 +1,2 @@
{"cache_key":"61322d7dd6c6f6ecd12429935bddd35cf29c24771475df989fb28ab6a579b26b","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goalNote","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"Goal note","text_hash":"1afb7855a394ef7078728de1c804d6b995413db4eafe7d74190076cb9ed2c9f5","tgt_lang":"nl","translated":"Doelnotitie","updated_at":"2026-05-29T21:02:33.311Z"}
{"cache_key":"e6f48f62b167833335d714cc2380e3b71081a01e7888678832b137bde4f4a747","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goal","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"Goal","text_hash":"cdbf6975e8a35b0d03558be6822dfae166482c24fb86b0433f60e8167f5c91e4","tgt_lang":"nl","translated":"Doel","updated_at":"2026-05-29T21:02:33.311Z"}

View File

@@ -1,11 +1,11 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-29T18:10:14.758Z",
"generatedAt": "2026-05-29T21:02:00.415Z",
"locale": "pl",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "1ddd9306566b37255394133ec48e05dfe9dd502fd57a34d3c9c96152331730e1",
"totalKeys": 1269,
"translatedKeys": 1269,
"sourceHash": "d51c5347e718c0fbe7353fccf82cefc0ef09b95b25acbc840a2f19b0c17b9296",
"totalKeys": 1271,
"translatedKeys": 1271,
"workflow": 1
}

2
ui/src/i18n/.i18n/pl.tm.jsonl generated Normal file
View File

@@ -0,0 +1,2 @@
{"cache_key":"6c2c3123ea0674e55540119ca248b61ae9e719eb05a4140cda4be98d621b39e2","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goalNote","source_path":"ui/src/i18n/locales/pl.ts","src_lang":"en","text":"Goal note","text_hash":"1afb7855a394ef7078728de1c804d6b995413db4eafe7d74190076cb9ed2c9f5","tgt_lang":"pl","translated":"Notatka dotycząca celu","updated_at":"2026-05-29T21:02:00.343Z"}
{"cache_key":"773fde7f04e9c87f69cc58984950611863dcac306cfbb15353f410e900533db6","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goal","source_path":"ui/src/i18n/locales/pl.ts","src_lang":"en","text":"Goal","text_hash":"cdbf6975e8a35b0d03558be6822dfae166482c24fb86b0433f60e8167f5c91e4","tgt_lang":"pl","translated":"Cel","updated_at":"2026-05-29T21:02:00.339Z"}

View File

@@ -1,11 +1,11 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-29T18:09:56.842Z",
"generatedAt": "2026-05-29T20:59:56.115Z",
"locale": "pt-BR",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "1ddd9306566b37255394133ec48e05dfe9dd502fd57a34d3c9c96152331730e1",
"totalKeys": 1269,
"translatedKeys": 1269,
"sourceHash": "d51c5347e718c0fbe7353fccf82cefc0ef09b95b25acbc840a2f19b0c17b9296",
"totalKeys": 1271,
"translatedKeys": 1271,
"workflow": 1
}

2
ui/src/i18n/.i18n/pt-BR.tm.jsonl generated Normal file
View File

@@ -0,0 +1,2 @@
{"cache_key":"0ec17d42d507c693995aa202c0aa2c38d62fb7aa0c0663165a00896f206b352d","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goal","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"Goal","text_hash":"cdbf6975e8a35b0d03558be6822dfae166482c24fb86b0433f60e8167f5c91e4","tgt_lang":"pt-BR","translated":"Objetivo","updated_at":"2026-05-29T20:59:56.109Z"}
{"cache_key":"3d4dcc4505a4934b851fcc18f8b4c646a4f02c41d5117c454c501a37f3004697","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goalNote","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"Goal note","text_hash":"1afb7855a394ef7078728de1c804d6b995413db4eafe7d74190076cb9ed2c9f5","tgt_lang":"pt-BR","translated":"Nota do objetivo","updated_at":"2026-05-29T20:59:56.109Z"}

View File

@@ -1,11 +1,11 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-29T18:10:16.172Z",
"generatedAt": "2026-05-29T21:02:14.037Z",
"locale": "th",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "1ddd9306566b37255394133ec48e05dfe9dd502fd57a34d3c9c96152331730e1",
"totalKeys": 1269,
"translatedKeys": 1269,
"sourceHash": "d51c5347e718c0fbe7353fccf82cefc0ef09b95b25acbc840a2f19b0c17b9296",
"totalKeys": 1271,
"translatedKeys": 1271,
"workflow": 1
}

2
ui/src/i18n/.i18n/th.tm.jsonl generated Normal file
View File

@@ -0,0 +1,2 @@
{"cache_key":"6d49f61e8daf29818cae027ae36136a2ebff9dc4e819e22d841c22f70ffae4cf","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goal","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"Goal","text_hash":"cdbf6975e8a35b0d03558be6822dfae166482c24fb86b0433f60e8167f5c91e4","tgt_lang":"th","translated":"เป้าหมาย","updated_at":"2026-05-29T21:02:14.030Z"}
{"cache_key":"ea705a9b3cb50a40d6d20d46fb239b99b37ae3745afebcef9150ae0922120b9f","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goalNote","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"Goal note","text_hash":"1afb7855a394ef7078728de1c804d6b995413db4eafe7d74190076cb9ed2c9f5","tgt_lang":"th","translated":"หมายเหตุเป้าหมาย","updated_at":"2026-05-29T21:02:14.030Z"}

View File

@@ -1,11 +1,11 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-29T18:10:10.102Z",
"generatedAt": "2026-05-29T21:01:26.035Z",
"locale": "tr",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "1ddd9306566b37255394133ec48e05dfe9dd502fd57a34d3c9c96152331730e1",
"totalKeys": 1269,
"translatedKeys": 1269,
"sourceHash": "d51c5347e718c0fbe7353fccf82cefc0ef09b95b25acbc840a2f19b0c17b9296",
"totalKeys": 1271,
"translatedKeys": 1271,
"workflow": 1
}

2
ui/src/i18n/.i18n/tr.tm.jsonl generated Normal file
View File

@@ -0,0 +1,2 @@
{"cache_key":"2425436cbd6df1e63399e29ead2cc39b64f74162add08ff66575a408f31b6868","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goalNote","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"Goal note","text_hash":"1afb7855a394ef7078728de1c804d6b995413db4eafe7d74190076cb9ed2c9f5","tgt_lang":"tr","translated":"Hedef notu","updated_at":"2026-05-29T21:01:25.865Z"}
{"cache_key":"89dc18d52451cd19d966cefb92e113df76f096c54ef2c6c44a745639484261be","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goal","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"Goal","text_hash":"cdbf6975e8a35b0d03558be6822dfae166482c24fb86b0433f60e8167f5c91e4","tgt_lang":"tr","translated":"Hedef","updated_at":"2026-05-29T21:01:25.865Z"}

View File

@@ -1,11 +1,11 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-29T18:10:11.525Z",
"generatedAt": "2026-05-29T21:01:33.693Z",
"locale": "uk",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "1ddd9306566b37255394133ec48e05dfe9dd502fd57a34d3c9c96152331730e1",
"totalKeys": 1269,
"translatedKeys": 1269,
"sourceHash": "d51c5347e718c0fbe7353fccf82cefc0ef09b95b25acbc840a2f19b0c17b9296",
"totalKeys": 1271,
"translatedKeys": 1271,
"workflow": 1
}

2
ui/src/i18n/.i18n/uk.tm.jsonl generated Normal file
View File

@@ -0,0 +1,2 @@
{"cache_key":"10837b29e8b086f86bfe06bb6fe52ff30536ac329d213f4fd6efddf12ac6c18d","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goalNote","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"Goal note","text_hash":"1afb7855a394ef7078728de1c804d6b995413db4eafe7d74190076cb9ed2c9f5","tgt_lang":"uk","translated":"Примітка до мети","updated_at":"2026-05-29T21:01:33.548Z"}
{"cache_key":"a7d851a926019e2bd61845a835cdb17a9a048573abc8e000276b4c4f79fe979d","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goal","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"Goal","text_hash":"cdbf6975e8a35b0d03558be6822dfae166482c24fb86b0433f60e8167f5c91e4","tgt_lang":"uk","translated":"Мета","updated_at":"2026-05-29T21:01:33.548Z"}

View File

@@ -1,11 +1,11 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-29T18:10:17.905Z",
"generatedAt": "2026-05-29T21:02:23.397Z",
"locale": "vi",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "1ddd9306566b37255394133ec48e05dfe9dd502fd57a34d3c9c96152331730e1",
"totalKeys": 1269,
"translatedKeys": 1269,
"sourceHash": "d51c5347e718c0fbe7353fccf82cefc0ef09b95b25acbc840a2f19b0c17b9296",
"totalKeys": 1271,
"translatedKeys": 1271,
"workflow": 1
}

2
ui/src/i18n/.i18n/vi.tm.jsonl generated Normal file
View File

@@ -0,0 +1,2 @@
{"cache_key":"37e1b972340d8c2b5d14328166f22dff7683c48e7e618680b94cf4a5846e3395","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goalNote","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"Goal note","text_hash":"1afb7855a394ef7078728de1c804d6b995413db4eafe7d74190076cb9ed2c9f5","tgt_lang":"vi","translated":"Ghi chú mục tiêu","updated_at":"2026-05-29T21:02:23.226Z"}
{"cache_key":"687bcca75327bc0dfd9262a5ad1ae85b09522d2ad3e1b408819c9c6ac632aeab","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goal","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"Goal","text_hash":"cdbf6975e8a35b0d03558be6822dfae166482c24fb86b0433f60e8167f5c91e4","tgt_lang":"vi","translated":"Mục tiêu","updated_at":"2026-05-29T21:02:23.226Z"}

View File

@@ -1,11 +1,11 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-29T18:09:54.364Z",
"generatedAt": "2026-05-29T20:59:45.228Z",
"locale": "zh-CN",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "1ddd9306566b37255394133ec48e05dfe9dd502fd57a34d3c9c96152331730e1",
"totalKeys": 1269,
"translatedKeys": 1269,
"sourceHash": "d51c5347e718c0fbe7353fccf82cefc0ef09b95b25acbc840a2f19b0c17b9296",
"totalKeys": 1271,
"translatedKeys": 1271,
"workflow": 1
}

2
ui/src/i18n/.i18n/zh-CN.tm.jsonl generated Normal file
View File

@@ -0,0 +1,2 @@
{"cache_key":"09ed15bb12e7e25d15337bb2d8b7e301d3838eea96f0fdc749c73cbb6e734b1f","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goal","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"Goal","text_hash":"cdbf6975e8a35b0d03558be6822dfae166482c24fb86b0433f60e8167f5c91e4","tgt_lang":"zh-CN","translated":"目标","updated_at":"2026-05-29T20:59:45.212Z"}
{"cache_key":"a22243320fa56f3a951b9a6a9a033efaee999643c0de514174713dbc0d6983b9","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goalNote","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"Goal note","text_hash":"1afb7855a394ef7078728de1c804d6b995413db4eafe7d74190076cb9ed2c9f5","tgt_lang":"zh-CN","translated":"目标备注","updated_at":"2026-05-29T20:59:45.213Z"}

View File

@@ -1,11 +1,11 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-29T18:09:55.672Z",
"generatedAt": "2026-05-29T20:59:52.034Z",
"locale": "zh-TW",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "1ddd9306566b37255394133ec48e05dfe9dd502fd57a34d3c9c96152331730e1",
"totalKeys": 1269,
"translatedKeys": 1269,
"sourceHash": "d51c5347e718c0fbe7353fccf82cefc0ef09b95b25acbc840a2f19b0c17b9296",
"totalKeys": 1271,
"translatedKeys": 1271,
"workflow": 1
}

2
ui/src/i18n/.i18n/zh-TW.tm.jsonl generated Normal file
View File

@@ -0,0 +1,2 @@
{"cache_key":"9979d886403e24df60997801e043da82148173019f9df8141fce453200283ac0","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goalNote","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"Goal note","text_hash":"1afb7855a394ef7078728de1c804d6b995413db4eafe7d74190076cb9ed2c9f5","tgt_lang":"zh-TW","translated":"目標備註","updated_at":"2026-05-29T20:59:51.845Z"}
{"cache_key":"d4a6c06bfdfedcffd1b6e7ad155ea4138c82ffa07146a06a5747b8b69310a82d","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.goal","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"Goal","text_hash":"cdbf6975e8a35b0d03558be6822dfae166482c24fb86b0433f60e8167f5c91e4","tgt_lang":"zh-TW","translated":"目標","updated_at":"2026-05-29T20:59:51.845Z"}

View File

@@ -184,6 +184,8 @@ export const ar: TranslationMap = {
updated: "تم التحديث",
tokens: "الرموز",
compaction: "الضغط",
goal: "الهدف",
goalNote: "ملاحظة الهدف",
thinking: "التفكير",
fast: "سريع",
verbose: "مطوّل",

View File

@@ -188,6 +188,8 @@ export const de: TranslationMap = {
updated: "Aktualisiert",
tokens: "Tokens",
compaction: "Kompaktierung",
goal: "Ziel",
goalNote: "Zielnotiz",
thinking: "Denken",
fast: "Schnell",
verbose: "Ausführlich",

View File

@@ -183,6 +183,8 @@ export const en: TranslationMap = {
updated: "Updated",
tokens: "Tokens",
compaction: "Compaction",
goal: "Goal",
goalNote: "Goal note",
thinking: "Thinking",
fast: "Fast",
verbose: "Verbose",

View File

@@ -185,6 +185,8 @@ export const es: TranslationMap = {
updated: "Actualizado",
tokens: "Tokens",
compaction: "Compactación",
goal: "Objetivo",
goalNote: "Nota del objetivo",
thinking: "Pensamiento",
fast: "Rápido",
verbose: "Detallado",

View File

@@ -186,6 +186,8 @@ export const fa: TranslationMap = {
updated: "به‌روزشده",
tokens: "توکن‌ها",
compaction: "فشرده‌سازی",
goal: "هدف",
goalNote: "یادداشت هدف",
thinking: "تفکر",
fast: "سریع",
verbose: "پرگویی",

View File

@@ -187,6 +187,8 @@ export const fr: TranslationMap = {
updated: "Mis à jour",
tokens: "Jetons",
compaction: "Compactage",
goal: "Objectif",
goalNote: "Note dobjectif",
thinking: "Réflexion",
fast: "Rapide",
verbose: "Détaillé",

View File

@@ -185,6 +185,8 @@ export const id: TranslationMap = {
updated: "Diperbarui",
tokens: "Token",
compaction: "Pemadatan",
goal: "Tujuan",
goalNote: "Catatan tujuan",
thinking: "Thinking",
fast: "Cepat",
verbose: "Verbose",

View File

@@ -185,6 +185,8 @@ export const it: TranslationMap = {
updated: "Aggiornato",
tokens: "Token",
compaction: "Compattazione",
goal: "Obiettivo",
goalNote: "Nota sull'obiettivo",
thinking: "Thinking",
fast: "Veloce",
verbose: "Dettagliato",

View File

@@ -188,6 +188,8 @@ export const ja_JP: TranslationMap = {
updated: "更新日時",
tokens: "トークン",
compaction: "圧縮",
goal: "目標",
goalNote: "目標メモ",
thinking: "Thinking",
fast: "高速",
verbose: "詳細",

View File

@@ -184,6 +184,8 @@ export const ko: TranslationMap = {
updated: "업데이트됨",
tokens: "토큰",
compaction: "압축",
goal: "목표",
goalNote: "목표 메모",
thinking: "생각 수준",
fast: "빠름",
verbose: "상세",

View File

@@ -187,6 +187,8 @@ export const nl: TranslationMap = {
updated: "Bijgewerkt",
tokens: "Tokens",
compaction: "Compactie",
goal: "Doel",
goalNote: "Doelnotitie",
thinking: "Denken",
fast: "Snel",
verbose: "Uitgebreid",

View File

@@ -186,6 +186,8 @@ export const pl: TranslationMap = {
updated: "Zaktualizowano",
tokens: "Tokeny",
compaction: "Kompaktowanie",
goal: "Cel",
goalNote: "Notatka dotycząca celu",
thinking: "Myślenie",
fast: "Szybko",
verbose: "Szczegółowo",

View File

@@ -185,6 +185,8 @@ export const pt_BR: TranslationMap = {
updated: "Atualizado",
tokens: "Tokens",
compaction: "Compactação",
goal: "Objetivo",
goalNote: "Nota do objetivo",
thinking: "Pensamento",
fast: "Rápido",
verbose: "Detalhado",

View File

@@ -183,6 +183,8 @@ export const th: TranslationMap = {
updated: "อัปเดตแล้ว",
tokens: "โทเค็น",
compaction: "การบีบอัด",
goal: "เป้าหมาย",
goalNote: "หมายเหตุเป้าหมาย",
thinking: "Thinking",
fast: "เร็ว",
verbose: "ละเอียด",

View File

@@ -187,6 +187,8 @@ export const tr: TranslationMap = {
updated: "Güncellendi",
tokens: "Tokenlar",
compaction: "Sıkıştırma",
goal: "Hedef",
goalNote: "Hedef notu",
thinking: "Düşünme",
fast: "Hızlı",
verbose: "Ayrıntılı",

View File

@@ -186,6 +186,8 @@ export const uk: TranslationMap = {
updated: "Оновлено",
tokens: "Токени",
compaction: "Стиснення",
goal: "Мета",
goalNote: "Примітка до мети",
thinking: "Обмірковування",
fast: "Швидко",
verbose: "Докладно",

View File

@@ -185,6 +185,8 @@ export const vi: TranslationMap = {
updated: "Đã cập nhật",
tokens: "Token",
compaction: "Nén",
goal: "Mục tiêu",
goalNote: "Ghi chú mục tiêu",
thinking: "Suy nghĩ",
fast: "Nhanh",
verbose: "Chi tiết",

View File

@@ -183,6 +183,8 @@ export const zh_CN: TranslationMap = {
updated: "已更新",
tokens: "Token",
compaction: "压缩",
goal: "目标",
goalNote: "目标备注",
thinking: "思考",
fast: "快速",
verbose: "详细",

View File

@@ -183,6 +183,8 @@ export const zh_TW: TranslationMap = {
updated: "已更新",
tokens: "Token",
compaction: "壓縮",
goal: "目標",
goalNote: "目標備註",
thinking: "思考",
fast: "快速",
verbose: "詳細",

View File

@@ -434,9 +434,9 @@ function sessionDetailItems(params: {
};
add(t("sessionsView.status"), row.status);
if (row.goal) {
details.push({ label: "Goal", value: formatGoalDetail(row.goal) });
details.push({ label: t("sessionsView.goal"), value: formatGoalDetail(row.goal) });
}
add("Goal note", row.goal?.lastStatusNote);
add(t("sessionsView.goalNote"), row.goal?.lastStatusNote);
add(t("sessionsView.model"), row.model);
add(t("sessionsView.provider"), row.modelProvider);
add(t("sessionsView.runtime"), formatRuntimeMs(row.runtimeMs));