chore: Enable more lint rules, disable some that trigger a lot. Will clean up later.
This commit is contained in:
parent
481f696a87
commit
15792b153f
292 changed files with 643 additions and 699 deletions
|
|
@ -7,13 +7,24 @@
|
||||||
],
|
],
|
||||||
"categories": {
|
"categories": {
|
||||||
"correctness": "error",
|
"correctness": "error",
|
||||||
"perf": "error"
|
"perf": "error",
|
||||||
|
"suspicious": "error",
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"eslint-plugin-unicorn/prefer-array-find": "off",
|
"eslint-plugin-unicorn/prefer-array-find": "off",
|
||||||
"eslint/no-await-in-loop": "off",
|
"eslint/no-await-in-loop": "off",
|
||||||
"oxc/no-accumulating-spread": "off",
|
"oxc/no-accumulating-spread": "off",
|
||||||
"oxc/no-map-spread": "off"
|
"oxc/no-map-spread": "off",
|
||||||
|
"typescript/no-unsafe-type-assertion": "off",
|
||||||
|
"typescript/no-unnecessary-template-expression": "off",
|
||||||
|
"unicorn/consistent-function-scoping": "off",
|
||||||
|
"typescript/no-extraneous-class": "off",
|
||||||
|
"oxc/no-async-endpoint-handlers": "off",
|
||||||
|
"eslint/no-useless-concat": "off",
|
||||||
|
"eslint/no-unused-vars": "off",
|
||||||
|
"eslint/no-new": "off",
|
||||||
|
"eslint/preserve-caught-error": "off",
|
||||||
|
"unicorn/require-post-message-target-origin": "off",
|
||||||
},
|
},
|
||||||
"ignorePatterns": ["src/canvas-host/a2ui/a2ui.bundle.js"]
|
"ignorePatterns": ["src/canvas-host/a2ui/a2ui.bundle.js"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,7 @@ export function createAnthropicPayloadLogger(params: {
|
||||||
|
|
||||||
const wrapStreamFn: AnthropicPayloadLogger["wrapStreamFn"] = (streamFn) => {
|
const wrapStreamFn: AnthropicPayloadLogger["wrapStreamFn"] = (streamFn) => {
|
||||||
const wrapped: StreamFn = (model, context, options) => {
|
const wrapped: StreamFn = (model, context, options) => {
|
||||||
if (!isAnthropicModel(model as Model<Api>)) {
|
if (!isAnthropicModel(model)) {
|
||||||
return streamFn(model, context, options);
|
return streamFn(model, context, options);
|
||||||
}
|
}
|
||||||
const nextOnPayload = (payload: unknown) => {
|
const nextOnPayload = (payload: unknown) => {
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ function applyReplacements(
|
||||||
replacements: Array<[number, number, string[]]>,
|
replacements: Array<[number, number, string[]]>,
|
||||||
): string[] {
|
): string[] {
|
||||||
const result = [...lines];
|
const result = [...lines];
|
||||||
for (const [startIndex, oldLen, newLines] of [...replacements].reverse()) {
|
for (const [startIndex, oldLen, newLines] of [...replacements].toReversed()) {
|
||||||
for (let i = 0; i < oldLen; i += 1) {
|
for (let i = 0; i < oldLen; i += 1) {
|
||||||
if (startIndex < result.length) {
|
if (startIndex < result.length) {
|
||||||
result.splice(startIndex, 1);
|
result.splice(startIndex, 1);
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,7 @@ export function buildAuthHealthSummary(params: {
|
||||||
warnAfterMs,
|
warnAfterMs,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.sort((a, b) => {
|
.toSorted((a, b) => {
|
||||||
if (a.provider !== b.provider) {
|
if (a.provider !== b.provider) {
|
||||||
return a.provider.localeCompare(b.provider);
|
return a.provider.localeCompare(b.provider);
|
||||||
}
|
}
|
||||||
|
|
@ -236,7 +236,7 @@ export function buildAuthHealthSummary(params: {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const providers = Array.from(providersMap.values()).sort((a, b) =>
|
const providers = Array.from(providersMap.values()).toSorted((a, b) =>
|
||||||
a.provider.localeCompare(b.provider),
|
a.provider.localeCompare(b.provider),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,7 @@ export function resolveAuthProfileDisplayLabel(params: {
|
||||||
const { cfg, store, profileId } = params;
|
const { cfg, store, profileId } = params;
|
||||||
const profile = store.profiles[profileId];
|
const profile = store.profiles[profileId];
|
||||||
const configEmail = cfg?.auth?.profiles?.[profileId]?.email?.trim();
|
const configEmail = cfg?.auth?.profiles?.[profileId]?.email?.trim();
|
||||||
const email =
|
const email = configEmail || (profile && "email" in profile ? profile.email?.trim() : undefined);
|
||||||
configEmail ||
|
|
||||||
(profile && "email" in profile ? (profile.email as string | undefined)?.trim() : undefined);
|
|
||||||
if (email) return `${profileId} (${email})`;
|
if (email) return `${profileId} (${email})`;
|
||||||
return profileId;
|
return profileId;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ async function refreshOAuthTokenWithLock(params: {
|
||||||
const newCredentials = await refreshQwenPortalCredentials(cred);
|
const newCredentials = await refreshQwenPortalCredentials(cred);
|
||||||
return { apiKey: newCredentials.access, newCredentials };
|
return { apiKey: newCredentials.access, newCredentials };
|
||||||
})()
|
})()
|
||||||
: await getOAuthApiKey(cred.provider as OAuthProvider, oauthCreds);
|
: await getOAuthApiKey(cred.provider, oauthCreds);
|
||||||
if (!result) return null;
|
if (!result) return null;
|
||||||
store.profiles[params.profileId] = {
|
store.profiles[params.profileId] = {
|
||||||
...cred,
|
...cred,
|
||||||
|
|
@ -233,6 +233,7 @@ export async function resolveApiKeyForProfile(params: {
|
||||||
`OAuth token refresh failed for ${cred.provider}: ${message}. ` +
|
`OAuth token refresh failed for ${cred.provider}: ${message}. ` +
|
||||||
"Please try again or re-authenticate." +
|
"Please try again or re-authenticate." +
|
||||||
(hint ? `\n\n${hint}` : ""),
|
(hint ? `\n\n${hint}` : ""),
|
||||||
|
{ cause: error },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ export function resolveAuthProfileOrder(params: {
|
||||||
}
|
}
|
||||||
|
|
||||||
const cooldownSorted = inCooldown
|
const cooldownSorted = inCooldown
|
||||||
.sort((a, b) => a.cooldownUntil - b.cooldownUntil)
|
.toSorted((a, b) => a.cooldownUntil - b.cooldownUntil)
|
||||||
.map((entry) => entry.profileId);
|
.map((entry) => entry.profileId);
|
||||||
|
|
||||||
const ordered = [...available, ...cooldownSorted];
|
const ordered = [...available, ...cooldownSorted];
|
||||||
|
|
@ -163,7 +163,7 @@ function orderProfilesByMode(order: string[], store: AuthProfileStore): string[]
|
||||||
// Primary sort: type preference (oauth > token > api_key).
|
// Primary sort: type preference (oauth > token > api_key).
|
||||||
// Secondary sort: lastUsed (oldest first for round-robin within type).
|
// Secondary sort: lastUsed (oldest first for round-robin within type).
|
||||||
const sorted = scored
|
const sorted = scored
|
||||||
.sort((a, b) => {
|
.toSorted((a, b) => {
|
||||||
// First by type (oauth > token > api_key)
|
// First by type (oauth > token > api_key)
|
||||||
if (a.typeScore !== b.typeScore) return a.typeScore - b.typeScore;
|
if (a.typeScore !== b.typeScore) return a.typeScore - b.typeScore;
|
||||||
// Then by lastUsed (oldest first)
|
// Then by lastUsed (oldest first)
|
||||||
|
|
@ -177,7 +177,7 @@ function orderProfilesByMode(order: string[], store: AuthProfileStore): string[]
|
||||||
profileId,
|
profileId,
|
||||||
cooldownUntil: resolveProfileUnusableUntil(store.usageStats?.[profileId] ?? {}) ?? now,
|
cooldownUntil: resolveProfileUnusableUntil(store.usageStats?.[profileId] ?? {}) ?? now,
|
||||||
}))
|
}))
|
||||||
.sort((a, b) => a.cooldownUntil - b.cooldownUntil)
|
.toSorted((a, b) => a.cooldownUntil - b.cooldownUntil)
|
||||||
.map((entry) => entry.profileId);
|
.map((entry) => entry.profileId);
|
||||||
|
|
||||||
return [...sorted, ...cooldownSorted];
|
return [...sorted, ...cooldownSorted];
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ export function suggestOAuthProfileIdForLegacyDefault(params: {
|
||||||
const byEmail = oauthProfiles.find((id) => {
|
const byEmail = oauthProfiles.find((id) => {
|
||||||
const cred = params.store.profiles[id];
|
const cred = params.store.profiles[id];
|
||||||
if (!cred || cred.type !== "oauth") return false;
|
if (!cred || cred.type !== "oauth") return false;
|
||||||
const email = (cred.email as string | undefined)?.trim();
|
const email = cred.email?.trim();
|
||||||
return email === configuredEmail || id === `${providerKey}:${configuredEmail}`;
|
return email === configuredEmail || id === `${providerKey}:${configuredEmail}`;
|
||||||
});
|
});
|
||||||
if (byEmail) return byEmail;
|
if (byEmail) return byEmail;
|
||||||
|
|
@ -93,11 +93,10 @@ export function repairOAuthProfileIdMismatch(params: {
|
||||||
}
|
}
|
||||||
|
|
||||||
const toCred = params.store.profiles[toProfileId];
|
const toCred = params.store.profiles[toProfileId];
|
||||||
const toEmail =
|
const toEmail = toCred?.type === "oauth" ? toCred.email?.trim() : undefined;
|
||||||
toCred?.type === "oauth" ? (toCred.email as string | undefined)?.trim() : undefined;
|
|
||||||
|
|
||||||
const nextProfiles = {
|
const nextProfiles = {
|
||||||
...(params.cfg.auth?.profiles as Record<string, AuthProfileConfig> | undefined),
|
...params.cfg.auth?.profiles,
|
||||||
} as Record<string, AuthProfileConfig>;
|
} as Record<string, AuthProfileConfig>;
|
||||||
delete nextProfiles[legacyProfileId];
|
delete nextProfiles[legacyProfileId];
|
||||||
nextProfiles[toProfileId] = {
|
nextProfiles[toProfileId] = {
|
||||||
|
|
|
||||||
|
|
@ -912,6 +912,7 @@ export function createExecTool(
|
||||||
if (!nodeQuery && String(err).includes("node required")) {
|
if (!nodeQuery && String(err).includes("node required")) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"exec host=node requires a node id when multiple nodes are available (set tools.exec.node or exec.node).",
|
"exec host=node requires a node id when multiple nodes are available (set tools.exec.node or exec.node).",
|
||||||
|
{ cause: err },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
|
|
@ -941,11 +942,11 @@ export function createExecTool(
|
||||||
let allowlistSatisfied = false;
|
let allowlistSatisfied = false;
|
||||||
if (hostAsk === "on-miss" && hostSecurity === "allowlist" && analysisOk) {
|
if (hostAsk === "on-miss" && hostSecurity === "allowlist" && analysisOk) {
|
||||||
try {
|
try {
|
||||||
const approvalsSnapshot = (await callGatewayTool(
|
const approvalsSnapshot = await callGatewayTool(
|
||||||
"exec.approvals.node.get",
|
"exec.approvals.node.get",
|
||||||
{ timeoutMs: 10_000 },
|
{ timeoutMs: 10_000 },
|
||||||
{ nodeId },
|
{ nodeId },
|
||||||
)) as { file?: unknown } | null;
|
);
|
||||||
const approvalsFile =
|
const approvalsFile =
|
||||||
approvalsSnapshot && typeof approvalsSnapshot === "object"
|
approvalsSnapshot && typeof approvalsSnapshot === "object"
|
||||||
? approvalsSnapshot.file
|
? approvalsSnapshot.file
|
||||||
|
|
@ -1016,7 +1017,7 @@ export function createExecTool(
|
||||||
void (async () => {
|
void (async () => {
|
||||||
let decision: string | null = null;
|
let decision: string | null = null;
|
||||||
try {
|
try {
|
||||||
const decisionResult = (await callGatewayTool(
|
const decisionResult = await callGatewayTool(
|
||||||
"exec.approval.request",
|
"exec.approval.request",
|
||||||
{ timeoutMs: DEFAULT_APPROVAL_REQUEST_TIMEOUT_MS },
|
{ timeoutMs: DEFAULT_APPROVAL_REQUEST_TIMEOUT_MS },
|
||||||
{
|
{
|
||||||
|
|
@ -1031,7 +1032,7 @@ export function createExecTool(
|
||||||
sessionKey: defaults?.sessionKey,
|
sessionKey: defaults?.sessionKey,
|
||||||
timeoutMs: DEFAULT_APPROVAL_TIMEOUT_MS,
|
timeoutMs: DEFAULT_APPROVAL_TIMEOUT_MS,
|
||||||
},
|
},
|
||||||
)) as { decision?: string } | null;
|
);
|
||||||
decision =
|
decision =
|
||||||
decisionResult && typeof decisionResult === "object"
|
decisionResult && typeof decisionResult === "object"
|
||||||
? (decisionResult.decision ?? null)
|
? (decisionResult.decision ?? null)
|
||||||
|
|
@ -1124,20 +1125,11 @@ export function createExecTool(
|
||||||
}
|
}
|
||||||
|
|
||||||
const startedAt = Date.now();
|
const startedAt = Date.now();
|
||||||
const raw = (await callGatewayTool(
|
const raw = await callGatewayTool(
|
||||||
"node.invoke",
|
"node.invoke",
|
||||||
{ timeoutMs: invokeTimeoutMs },
|
{ timeoutMs: invokeTimeoutMs },
|
||||||
buildInvokeParams(false, null),
|
buildInvokeParams(false, null),
|
||||||
)) as {
|
);
|
||||||
payload?: {
|
|
||||||
exitCode?: number;
|
|
||||||
timedOut?: boolean;
|
|
||||||
success?: boolean;
|
|
||||||
stdout?: string;
|
|
||||||
stderr?: string;
|
|
||||||
error?: string | null;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
const payload = raw?.payload ?? {};
|
const payload = raw?.payload ?? {};
|
||||||
return {
|
return {
|
||||||
content: [
|
content: [
|
||||||
|
|
@ -1197,7 +1189,7 @@ export function createExecTool(
|
||||||
void (async () => {
|
void (async () => {
|
||||||
let decision: string | null = null;
|
let decision: string | null = null;
|
||||||
try {
|
try {
|
||||||
const decisionResult = (await callGatewayTool(
|
const decisionResult = await callGatewayTool(
|
||||||
"exec.approval.request",
|
"exec.approval.request",
|
||||||
{ timeoutMs: DEFAULT_APPROVAL_REQUEST_TIMEOUT_MS },
|
{ timeoutMs: DEFAULT_APPROVAL_REQUEST_TIMEOUT_MS },
|
||||||
{
|
{
|
||||||
|
|
@ -1212,7 +1204,7 @@ export function createExecTool(
|
||||||
sessionKey: defaults?.sessionKey,
|
sessionKey: defaults?.sessionKey,
|
||||||
timeoutMs: DEFAULT_APPROVAL_TIMEOUT_MS,
|
timeoutMs: DEFAULT_APPROVAL_TIMEOUT_MS,
|
||||||
},
|
},
|
||||||
)) as { decision?: string } | null;
|
);
|
||||||
decision =
|
decision =
|
||||||
decisionResult && typeof decisionResult === "object"
|
decisionResult && typeof decisionResult === "object"
|
||||||
? (decisionResult.decision ?? null)
|
? (decisionResult.decision ?? null)
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ export function createProcessTool(
|
||||||
exitSignal: s.exitSignal ?? undefined,
|
exitSignal: s.exitSignal ?? undefined,
|
||||||
}));
|
}));
|
||||||
const lines = [...running, ...finished]
|
const lines = [...running, ...finished]
|
||||||
.sort((a, b) => b.startedAt - a.startedAt)
|
.toSorted((a, b) => b.startedAt - a.startedAt)
|
||||||
.map((s) => {
|
.map((s) => {
|
||||||
const label = s.name ? truncateMiddle(s.name, 80) : truncateMiddle(s.command, 120);
|
const label = s.name ? truncateMiddle(s.name, 80) : truncateMiddle(s.command, 120);
|
||||||
return `${s.sessionId} ${pad(s.status, 9)} ${formatDuration(s.runtimeMs)} :: ${label}`;
|
return `${s.sessionId} ${pad(s.status, 9)} ${formatDuration(s.runtimeMs)} :: ${label}`;
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ function normalizeProviderFilter(filter?: string[]): string[] {
|
||||||
const normalized = new Set(
|
const normalized = new Set(
|
||||||
filter.map((entry) => entry.trim().toLowerCase()).filter((entry) => entry.length > 0),
|
filter.map((entry) => entry.trim().toLowerCase()).filter((entry) => entry.length > 0),
|
||||||
);
|
);
|
||||||
return Array.from(normalized).sort();
|
return Array.from(normalized).toSorted();
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildCacheKey(params: {
|
function buildCacheKey(params: {
|
||||||
|
|
@ -168,7 +168,7 @@ export async function discoverBedrockModels(params: {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return discovered.sort((a, b) => a.name.localeCompare(b.name));
|
return discovered.toSorted((a, b) => a.name.localeCompare(b.name));
|
||||||
})();
|
})();
|
||||||
|
|
||||||
if (refreshIntervalSeconds > 0) {
|
if (refreshIntervalSeconds > 0) {
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,7 @@ function stableStringify(value: unknown): string {
|
||||||
return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
|
return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
|
||||||
}
|
}
|
||||||
const record = value as Record<string, unknown>;
|
const record = value as Record<string, unknown>;
|
||||||
const keys = Object.keys(record).sort();
|
const keys = Object.keys(record).toSorted();
|
||||||
const entries = keys.map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`);
|
const entries = keys.map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`);
|
||||||
return `{${entries.join(",")}}`;
|
return `{${entries.join(",")}}`;
|
||||||
}
|
}
|
||||||
|
|
@ -249,9 +249,9 @@ export function createCacheTrace(params: CacheTraceInit): CacheTrace | null {
|
||||||
const wrapped: StreamFn = (model, context, options) => {
|
const wrapped: StreamFn = (model, context, options) => {
|
||||||
recordStage("stream:context", {
|
recordStage("stream:context", {
|
||||||
model: {
|
model: {
|
||||||
id: (model as Model<Api>)?.id,
|
id: model?.id,
|
||||||
provider: (model as Model<Api>)?.provider,
|
provider: model?.provider,
|
||||||
api: (model as Model<Api>)?.api,
|
api: model?.api,
|
||||||
},
|
},
|
||||||
system: (context as { system?: unknown }).system,
|
system: (context as { system?: unknown }).system,
|
||||||
messages: (context as { messages?: AgentMessage[] }).messages ?? [],
|
messages: (context as { messages?: AgentMessage[] }).messages ?? [],
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,7 @@ function buildModelAliasLines(cfg?: OpenClawConfig) {
|
||||||
entries.push({ alias, model });
|
entries.push({ alias, model });
|
||||||
}
|
}
|
||||||
return entries
|
return entries
|
||||||
.sort((a, b) => a.alias.localeCompare(b.alias))
|
.toSorted((a, b) => a.alias.localeCompare(b.alias))
|
||||||
.map((entry) => `- ${entry.alias}: ${entry.model}`);
|
.map((entry) => `- ${entry.alias}: ${entry.model}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -228,7 +228,7 @@ export function normalizeCliModel(modelId: string, backend: CliBackendConfig): s
|
||||||
|
|
||||||
function toUsage(raw: Record<string, unknown>): CliUsage | undefined {
|
function toUsage(raw: Record<string, unknown>): CliUsage | undefined {
|
||||||
const pick = (key: string) =>
|
const pick = (key: string) =>
|
||||||
typeof raw[key] === "number" && raw[key] > 0 ? (raw[key] as number) : undefined;
|
typeof raw[key] === "number" && raw[key] > 0 ? raw[key] : undefined;
|
||||||
const input = pick("input_tokens") ?? pick("inputTokens");
|
const input = pick("input_tokens") ?? pick("inputTokens");
|
||||||
const output = pick("output_tokens") ?? pick("outputTokens");
|
const output = pick("output_tokens") ?? pick("outputTokens");
|
||||||
const cacheRead =
|
const cacheRead =
|
||||||
|
|
|
||||||
|
|
@ -128,8 +128,8 @@ describe("pruneHistoryForContextShare", () => {
|
||||||
const allIds = [
|
const allIds = [
|
||||||
...pruned.droppedMessagesList.map((m) => m.timestamp),
|
...pruned.droppedMessagesList.map((m) => m.timestamp),
|
||||||
...pruned.messages.map((m) => m.timestamp),
|
...pruned.messages.map((m) => m.timestamp),
|
||||||
].sort((a, b) => a - b);
|
].toSorted((a, b) => a - b);
|
||||||
const originalIds = messages.map((m) => m.timestamp).sort((a, b) => a - b);
|
const originalIds = messages.map((m) => m.timestamp).toSorted((a, b) => a - b);
|
||||||
expect(allIds).toEqual(originalIds);
|
expect(allIds).toEqual(originalIds);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,13 +34,11 @@ function resolveProviderConfig(
|
||||||
const matched = Object.entries(providers).find(
|
const matched = Object.entries(providers).find(
|
||||||
([key]) => normalizeProviderId(key) === normalized,
|
([key]) => normalizeProviderId(key) === normalized,
|
||||||
);
|
);
|
||||||
return matched?.[1] as ModelProviderConfig | undefined;
|
return matched?.[1];
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
(providers[normalized] as ModelProviderConfig | undefined) ??
|
(providers[normalized] as ModelProviderConfig | undefined) ??
|
||||||
(Object.entries(providers).find(([key]) => normalizeProviderId(key) === normalized)?.[1] as
|
Object.entries(providers).find(([key]) => normalizeProviderId(key) === normalized)?.[1]
|
||||||
| ModelProviderConfig
|
|
||||||
| undefined)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,9 +83,7 @@ export async function loadModelCatalog(params?: {
|
||||||
? entry.contextWindow
|
? entry.contextWindow
|
||||||
: undefined;
|
: undefined;
|
||||||
const reasoning = typeof entry?.reasoning === "boolean" ? entry.reasoning : undefined;
|
const reasoning = typeof entry?.reasoning === "boolean" ? entry.reasoning : undefined;
|
||||||
const input = Array.isArray(entry?.input)
|
const input = Array.isArray(entry?.input) ? entry.input : undefined;
|
||||||
? (entry.input as Array<"text" | "image">)
|
|
||||||
: undefined;
|
|
||||||
models.push({ id, name, provider, contextWindow, reasoning, input });
|
models.push({ id, name, provider, contextWindow, reasoning, input });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ export function normalizeModelCompat(model: Model<Api>): Model<Api> {
|
||||||
const isZai = model.provider === "zai" || baseUrl.includes("api.z.ai");
|
const isZai = model.provider === "zai" || baseUrl.includes("api.z.ai");
|
||||||
if (!isZai || !isOpenAiCompletionsModel(model)) return model;
|
if (!isZai || !isOpenAiCompletionsModel(model)) return model;
|
||||||
|
|
||||||
const openaiModel = model as Model<"openai-completions">;
|
const openaiModel = model;
|
||||||
const compat = openaiModel.compat ?? undefined;
|
const compat = openaiModel.compat ?? undefined;
|
||||||
if (compat?.supportsDeveloperRole === false) return model;
|
if (compat?.supportsDeveloperRole === false) return model;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,7 @@ export async function runWithModelFallback<T>(params: {
|
||||||
let lastError: unknown;
|
let lastError: unknown;
|
||||||
|
|
||||||
for (let i = 0; i < candidates.length; i += 1) {
|
for (let i = 0; i < candidates.length; i += 1) {
|
||||||
const candidate = candidates[i] as ModelCandidate;
|
const candidate = candidates[i];
|
||||||
if (authStore) {
|
if (authStore) {
|
||||||
const profileIds = resolveAuthProfileOrder({
|
const profileIds = resolveAuthProfileOrder({
|
||||||
cfg: params.cfg,
|
cfg: params.cfg,
|
||||||
|
|
@ -330,7 +330,7 @@ export async function runWithImageModelFallback<T>(params: {
|
||||||
let lastError: unknown;
|
let lastError: unknown;
|
||||||
|
|
||||||
for (let i = 0; i < candidates.length; i += 1) {
|
for (let i = 0; i < candidates.length; i += 1) {
|
||||||
const candidate = candidates[i] as ModelCandidate;
|
const candidate = candidates[i];
|
||||||
try {
|
try {
|
||||||
const result = await params.run(candidate.provider, candidate.model);
|
const result = await params.run(candidate.provider, candidate.model);
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -325,7 +325,7 @@ async function mapWithConcurrency<T, R>(
|
||||||
opts?: { onProgress?: (completed: number, total: number) => void },
|
opts?: { onProgress?: (completed: number, total: number) => void },
|
||||||
): Promise<R[]> {
|
): Promise<R[]> {
|
||||||
const limit = Math.max(1, Math.floor(concurrency));
|
const limit = Math.max(1, Math.floor(concurrency));
|
||||||
const results = Array.from({ length: items.length }) as R[];
|
const results = Array.from({ length: items.length });
|
||||||
let nextIndex = 0;
|
let nextIndex = 0;
|
||||||
let completed = 0;
|
let completed = 0;
|
||||||
|
|
||||||
|
|
@ -334,7 +334,7 @@ async function mapWithConcurrency<T, R>(
|
||||||
const current = nextIndex;
|
const current = nextIndex;
|
||||||
nextIndex += 1;
|
nextIndex += 1;
|
||||||
if (current >= items.length) return;
|
if (current >= items.length) return;
|
||||||
results[current] = await fn(items[current] as T, current);
|
results[current] = await fn(items[current], current);
|
||||||
completed += 1;
|
completed += 1;
|
||||||
opts?.onProgress?.(completed, items.length);
|
opts?.onProgress?.(completed, items.length);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ export async function ensureOpenClawModelsJson(
|
||||||
const cfg = config ?? loadConfig();
|
const cfg = config ?? loadConfig();
|
||||||
const agentDir = agentDirOverride?.trim() ? agentDirOverride.trim() : resolveOpenClawAgentDir();
|
const agentDir = agentDirOverride?.trim() ? agentDirOverride.trim() : resolveOpenClawAgentDir();
|
||||||
|
|
||||||
const explicitProviders = (cfg.models?.providers ?? {}) as Record<string, ProviderConfig>;
|
const explicitProviders = cfg.models?.providers ?? {};
|
||||||
const implicitProviders = await resolveImplicitProviders({ agentDir });
|
const implicitProviders = await resolveImplicitProviders({ agentDir });
|
||||||
const providers: Record<string, ProviderConfig> = mergeProviders({
|
const providers: Record<string, ProviderConfig> = mergeProviders({
|
||||||
implicit: implicitProviders,
|
implicit: implicitProviders,
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,7 @@ describeLive("live models (profile keys)", () => {
|
||||||
const agentDir = resolveOpenClawAgentDir();
|
const agentDir = resolveOpenClawAgentDir();
|
||||||
const authStorage = discoverAuthStorage(agentDir);
|
const authStorage = discoverAuthStorage(agentDir);
|
||||||
const modelRegistry = discoverModels(authStorage, agentDir);
|
const modelRegistry = discoverModels(authStorage, agentDir);
|
||||||
const models = modelRegistry.getAll() as Array<Model<Api>>;
|
const models = modelRegistry.getAll();
|
||||||
|
|
||||||
const rawModels = process.env.OPENCLAW_LIVE_MODELS?.trim();
|
const rawModels = process.env.OPENCLAW_LIVE_MODELS?.trim();
|
||||||
const useModern = rawModels === "modern" || rawModels === "all";
|
const useModern = rawModels === "modern" || rawModels === "all";
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,7 @@ function stableStringify(value: unknown): string {
|
||||||
return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
|
return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
|
||||||
}
|
}
|
||||||
const record = value as Record<string, unknown>;
|
const record = value as Record<string, unknown>;
|
||||||
const keys = Object.keys(record).sort();
|
const keys = Object.keys(record).toSorted();
|
||||||
const entries = keys.map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`);
|
const entries = keys.map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`);
|
||||||
return `{${entries.join(",")}}`;
|
return `{${entries.join(",")}}`;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ export async function sanitizeSessionMessagesImages(
|
||||||
const toolMsg = msg as Extract<AgentMessage, { role: "toolResult" }>;
|
const toolMsg = msg as Extract<AgentMessage, { role: "toolResult" }>;
|
||||||
const content = Array.isArray(toolMsg.content) ? toolMsg.content : [];
|
const content = Array.isArray(toolMsg.content) ? toolMsg.content : [];
|
||||||
const nextContent = (await sanitizeContentBlocksImages(
|
const nextContent = (await sanitizeContentBlocksImages(
|
||||||
content as ContentBlock[],
|
content,
|
||||||
label,
|
label,
|
||||||
)) as unknown as typeof toolMsg.content;
|
)) as unknown as typeof toolMsg.content;
|
||||||
out.push({ ...toolMsg, content: nextContent });
|
out.push({ ...toolMsg, content: nextContent });
|
||||||
|
|
|
||||||
|
|
@ -87,16 +87,16 @@ export function downgradeOpenAIReasoningBlocks(messages: AgentMessage[]): AgentM
|
||||||
}
|
}
|
||||||
const record = block as OpenAIThinkingBlock;
|
const record = block as OpenAIThinkingBlock;
|
||||||
if (record.type !== "thinking") {
|
if (record.type !== "thinking") {
|
||||||
nextContent.push(block as AssistantContentBlock);
|
nextContent.push(block);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const signature = parseOpenAIReasoningSignature(record.thinkingSignature);
|
const signature = parseOpenAIReasoningSignature(record.thinkingSignature);
|
||||||
if (!signature) {
|
if (!signature) {
|
||||||
nextContent.push(block as AssistantContentBlock);
|
nextContent.push(block);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (hasFollowingNonThinkingBlock(assistantMsg.content, i)) {
|
if (hasFollowingNonThinkingBlock(assistantMsg.content, i)) {
|
||||||
nextContent.push(block as AssistantContentBlock);
|
nextContent.push(block);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
changed = true;
|
changed = true;
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ function createStreamFnWithExtraParams(
|
||||||
|
|
||||||
const underlying = baseStreamFn ?? streamSimple;
|
const underlying = baseStreamFn ?? streamSimple;
|
||||||
const wrappedStreamFn: StreamFn = (model, context, options) =>
|
const wrappedStreamFn: StreamFn = (model, context, options) =>
|
||||||
underlying(model as Model<Api>, context, {
|
underlying(model, context, {
|
||||||
...streamParams,
|
...streamParams,
|
||||||
...options,
|
...options,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ function sanitizeAntigravityThinkingBlocks(messages: AgentMessage[]): AgentMessa
|
||||||
out.push(msg);
|
out.push(msg);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const assistant = msg as Extract<AgentMessage, { role: "assistant" }>;
|
const assistant = msg;
|
||||||
if (!Array.isArray(assistant.content)) {
|
if (!Array.isArray(assistant.content)) {
|
||||||
out.push(msg);
|
out.push(msg);
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ export function buildModelAliasLines(cfg?: OpenClawConfig) {
|
||||||
entries.push({ alias, model });
|
entries.push({ alias, model });
|
||||||
}
|
}
|
||||||
return entries
|
return entries
|
||||||
.sort((a, b) => a.alias.localeCompare(b.alias))
|
.toSorted((a, b) => a.alias.localeCompare(b.alias))
|
||||||
.map((entry) => `- ${entry.alias}: ${entry.model}`);
|
.map((entry) => `- ${entry.alias}: ${entry.model}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,11 +82,7 @@ vi.mock("../defaults.js", () => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("../failover-error.js", () => ({
|
vi.mock("../failover-error.js", () => ({
|
||||||
FailoverError: class extends Error {
|
FailoverError: class extends Error {},
|
||||||
constructor(msg: string) {
|
|
||||||
super(msg);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resolveFailoverStatus: vi.fn(),
|
resolveFailoverStatus: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -854,7 +854,7 @@ export async function runEmbeddedAttempt(
|
||||||
|
|
||||||
const lastAssistant = messagesSnapshot
|
const lastAssistant = messagesSnapshot
|
||||||
.slice()
|
.slice()
|
||||||
.reverse()
|
.toReversed()
|
||||||
.find((m) => (m as AgentMessage)?.role === "assistant") as AssistantMessage | undefined;
|
.find((m) => (m as AgentMessage)?.role === "assistant") as AssistantMessage | undefined;
|
||||||
|
|
||||||
const toolMetasNormalized = toolMetas
|
const toolMetasNormalized = toolMetas
|
||||||
|
|
|
||||||
|
|
@ -162,7 +162,7 @@ export function handleMessageEnd(
|
||||||
const msg = evt.message;
|
const msg = evt.message;
|
||||||
if (msg?.role !== "assistant") return;
|
if (msg?.role !== "assistant") return;
|
||||||
|
|
||||||
const assistantMessage = msg as AssistantMessage;
|
const assistantMessage = msg;
|
||||||
promoteThinkingTagsToBlocks(assistantMessage);
|
promoteThinkingTagsToBlocks(assistantMessage);
|
||||||
|
|
||||||
const rawText = extractAssistantText(assistantMessage);
|
const rawText = extractAssistantText(assistantMessage);
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export function setCompactionSafeguardRuntime(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = sessionManager as object;
|
const key = sessionManager;
|
||||||
if (value === null) {
|
if (value === null) {
|
||||||
REGISTRY.delete(key);
|
REGISTRY.delete(key);
|
||||||
return;
|
return;
|
||||||
|
|
@ -30,5 +30,5 @@ export function getCompactionSafeguardRuntime(
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return REGISTRY.get(sessionManager as object) ?? null;
|
return REGISTRY.get(sessionManager) ?? null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,8 +117,8 @@ function computeFileLists(fileOps: FileOperations): {
|
||||||
modifiedFiles: string[];
|
modifiedFiles: string[];
|
||||||
} {
|
} {
|
||||||
const modified = new Set([...fileOps.edited, ...fileOps.written]);
|
const modified = new Set([...fileOps.edited, ...fileOps.written]);
|
||||||
const readFiles = [...fileOps.read].filter((f) => !modified.has(f)).sort();
|
const readFiles = [...fileOps.read].filter((f) => !modified.has(f)).toSorted();
|
||||||
const modifiedFiles = [...modified].sort();
|
const modifiedFiles = [...modified].toSorted();
|
||||||
return { readFiles, modifiedFiles };
|
return { readFiles, modifiedFiles };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
|
||||||
import type { ContextEvent, ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
import type { ContextEvent, ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
||||||
|
|
||||||
import { pruneContextMessages } from "./pruner.js";
|
import { pruneContextMessages } from "./pruner.js";
|
||||||
|
|
@ -21,7 +20,7 @@ export default function contextPruningExtension(api: ExtensionAPI): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
const next = pruneContextMessages({
|
const next = pruneContextMessages({
|
||||||
messages: event.messages as AgentMessage[],
|
messages: event.messages,
|
||||||
settings: runtime.settings,
|
settings: runtime.settings,
|
||||||
ctx,
|
ctx,
|
||||||
isToolPrunable: runtime.isToolPrunable,
|
isToolPrunable: runtime.isToolPrunable,
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ export function setContextPruningRuntime(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = sessionManager as object;
|
const key = sessionManager;
|
||||||
if (value === null) {
|
if (value === null) {
|
||||||
REGISTRY.delete(key);
|
REGISTRY.delete(key);
|
||||||
return;
|
return;
|
||||||
|
|
@ -36,5 +36,5 @@ export function getContextPruningRuntime(
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return REGISTRY.get(sessionManager as object) ?? null;
|
return REGISTRY.get(sessionManager) ?? null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ export function toToolDefinitions(tools: AnyAgentTool[]): ToolDefinition[] {
|
||||||
label: tool.label ?? name,
|
label: tool.label ?? name,
|
||||||
description: tool.description ?? "",
|
description: tool.description ?? "",
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: TypeBox schema from pi-agent-core uses a different module instance.
|
// biome-ignore lint/suspicious/noExplicitAny: TypeBox schema from pi-agent-core uses a different module instance.
|
||||||
parameters: tool.parameters as any,
|
parameters: tool.parameters,
|
||||||
execute: async (
|
execute: async (
|
||||||
toolCallId,
|
toolCallId,
|
||||||
params,
|
params,
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ describe("createOpenClawCodingTools", () => {
|
||||||
return {
|
return {
|
||||||
name: tool.name,
|
name: tool.name,
|
||||||
type: schema?.type,
|
type: schema?.type,
|
||||||
keys: schema ? Object.keys(schema).sort() : null,
|
keys: schema ? Object.keys(schema).toSorted() : null,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter((entry) => entry.type !== "object");
|
.filter((entry) => entry.type !== "object");
|
||||||
|
|
|
||||||
|
|
@ -272,11 +272,7 @@ export function createOpenClawReadTool(base: AnyAgentTool): AnyAgentTool {
|
||||||
normalized ??
|
normalized ??
|
||||||
(params && typeof params === "object" ? (params as Record<string, unknown>) : undefined);
|
(params && typeof params === "object" ? (params as Record<string, unknown>) : undefined);
|
||||||
assertRequiredParams(record, CLAUDE_PARAM_GROUPS.read, base.name);
|
assertRequiredParams(record, CLAUDE_PARAM_GROUPS.read, base.name);
|
||||||
const result = (await base.execute(
|
const result = await base.execute(toolCallId, normalized ?? params, signal);
|
||||||
toolCallId,
|
|
||||||
normalized ?? params,
|
|
||||||
signal,
|
|
||||||
)) as AgentToolResult<unknown>;
|
|
||||||
const filePath = typeof record?.path === "string" ? String(record.path) : "<unknown>";
|
const filePath = typeof record?.path === "string" ? String(record.path) : "<unknown>";
|
||||||
const normalizedResult = await normalizeReadImageResult(result, filePath);
|
const normalizedResult = await normalizeReadImageResult(result, filePath);
|
||||||
return sanitizeToolResultImages(normalizedResult, `read:${filePath}`);
|
return sanitizeToolResultImages(normalizedResult, `read:${filePath}`);
|
||||||
|
|
|
||||||
|
|
@ -247,7 +247,7 @@ export function createOpenClawCodingTools(options?: {
|
||||||
// Wrap with param normalization for Claude Code compatibility
|
// Wrap with param normalization for Claude Code compatibility
|
||||||
return [wrapToolParamNormalization(createEditTool(workspaceRoot), CLAUDE_PARAM_GROUPS.edit)];
|
return [wrapToolParamNormalization(createEditTool(workspaceRoot), CLAUDE_PARAM_GROUPS.edit)];
|
||||||
}
|
}
|
||||||
return [tool as AnyAgentTool];
|
return [tool];
|
||||||
});
|
});
|
||||||
const { cleanupMs: cleanupMsOverride, ...execDefaults } = options?.exec ?? {};
|
const { cleanupMs: cleanupMsOverride, ...execDefaults } = options?.exec ?? {};
|
||||||
const execTool = createExecTool({
|
const execTool = createExecTool({
|
||||||
|
|
@ -338,13 +338,13 @@ export function createOpenClawCodingTools(options?: {
|
||||||
];
|
];
|
||||||
const coreToolNames = new Set(
|
const coreToolNames = new Set(
|
||||||
tools
|
tools
|
||||||
.filter((tool) => !getPluginToolMeta(tool as AnyAgentTool))
|
.filter((tool) => !getPluginToolMeta(tool))
|
||||||
.map((tool) => normalizeToolName(tool.name))
|
.map((tool) => normalizeToolName(tool.name))
|
||||||
.filter(Boolean),
|
.filter(Boolean),
|
||||||
);
|
);
|
||||||
const pluginGroups = buildPluginToolGroups({
|
const pluginGroups = buildPluginToolGroups({
|
||||||
tools,
|
tools,
|
||||||
toolMeta: (tool) => getPluginToolMeta(tool as AnyAgentTool),
|
toolMeta: (tool) => getPluginToolMeta(tool),
|
||||||
});
|
});
|
||||||
const resolvePolicy = (policy: typeof profilePolicy, label: string) => {
|
const resolvePolicy = (policy: typeof profilePolicy, label: string) => {
|
||||||
const resolved = stripPluginOnlyAllowlist(policy, pluginGroups, coreToolNames);
|
const resolved = stripPluginOnlyAllowlist(policy, pluginGroups, coreToolNames);
|
||||||
|
|
|
||||||
|
|
@ -20,14 +20,14 @@ function normalizeForHash(value: unknown): unknown {
|
||||||
.filter((item): item is unknown => item !== undefined);
|
.filter((item): item is unknown => item !== undefined);
|
||||||
const primitives = normalized.filter(isPrimitive);
|
const primitives = normalized.filter(isPrimitive);
|
||||||
if (primitives.length === normalized.length) {
|
if (primitives.length === normalized.length) {
|
||||||
return [...primitives].sort((a, b) =>
|
return [...primitives].toSorted((a, b) =>
|
||||||
primitiveToString(a).localeCompare(primitiveToString(b)),
|
primitiveToString(a).localeCompare(primitiveToString(b)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return normalized;
|
return normalized;
|
||||||
}
|
}
|
||||||
if (value && typeof value === "object") {
|
if (value && typeof value === "object") {
|
||||||
const entries = Object.entries(value).sort(([a], [b]) => a.localeCompare(b));
|
const entries = Object.entries(value).toSorted(([a], [b]) => a.localeCompare(b));
|
||||||
const normalized: Record<string, unknown> = {};
|
const normalized: Record<string, unknown> = {};
|
||||||
for (const [key, entryValue] of entries) {
|
for (const [key, entryValue] of entries) {
|
||||||
const next = normalizeForHash(entryValue);
|
const next = normalizeForHash(entryValue);
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ export function repairToolUseResultPairing(messages: AgentMessage[]): ToolUseRep
|
||||||
};
|
};
|
||||||
|
|
||||||
for (let i = 0; i < messages.length; i += 1) {
|
for (let i = 0; i < messages.length; i += 1) {
|
||||||
const msg = messages[i] as AgentMessage;
|
const msg = messages[i];
|
||||||
if (!msg || typeof msg !== "object") {
|
if (!msg || typeof msg !== "object") {
|
||||||
out.push(msg);
|
out.push(msg);
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -129,7 +129,7 @@ export function repairToolUseResultPairing(messages: AgentMessage[]): ToolUseRep
|
||||||
|
|
||||||
let j = i + 1;
|
let j = i + 1;
|
||||||
for (; j < messages.length; j += 1) {
|
for (; j < messages.length; j += 1) {
|
||||||
const next = messages[j] as AgentMessage;
|
const next = messages[j];
|
||||||
if (!next || typeof next !== "object") {
|
if (!next || typeof next !== "object") {
|
||||||
remainder.push(next);
|
remainder.push(next);
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ describe("buildWorkspaceSkillCommandSpecs", () => {
|
||||||
reservedNames: new Set(["help"]),
|
reservedNames: new Set(["help"]),
|
||||||
});
|
});
|
||||||
|
|
||||||
const names = commands.map((entry) => entry.name).sort();
|
const names = commands.map((entry) => entry.name).toSorted();
|
||||||
expect(names).toEqual(["hello_world", "hello_world_2", "help_2"]);
|
expect(names).toEqual(["hello_world", "hello_world_2", "help_2"]);
|
||||||
expect(commands.find((entry) => entry.skillName === "hidden-skill")).toBeUndefined();
|
expect(commands.find((entry) => entry.skillName === "hidden-skill")).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ describe("buildWorkspaceSkillSnapshot", () => {
|
||||||
|
|
||||||
expect(snapshot.prompt).toContain("visible-skill");
|
expect(snapshot.prompt).toContain("visible-skill");
|
||||||
expect(snapshot.prompt).not.toContain("hidden-skill");
|
expect(snapshot.prompt).not.toContain("hidden-skill");
|
||||||
expect(snapshot.skills.map((skill) => skill.name).sort()).toEqual([
|
expect(snapshot.skills.map((skill) => skill.name).toSorted()).toEqual([
|
||||||
"hidden-skill",
|
"hidden-skill",
|
||||||
"visible-skill",
|
"visible-skill",
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ export function resolveSkillsInstallPreferences(config?: OpenClawConfig) {
|
||||||
const manager = managerRaw.toLowerCase();
|
const manager = managerRaw.toLowerCase();
|
||||||
const nodeManager =
|
const nodeManager =
|
||||||
manager === "pnpm" || manager === "yarn" || manager === "bun" || manager === "npm"
|
manager === "pnpm" || manager === "yarn" || manager === "bun" || manager === "npm"
|
||||||
? (manager as "npm" | "pnpm" | "yarn" | "bun")
|
? manager
|
||||||
: "npm";
|
: "npm";
|
||||||
return { preferBrew, nodeManager };
|
return { preferBrew, nodeManager };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ export function resolveConfigPath(config: OpenClawConfig | undefined, pathStr: s
|
||||||
export function isConfigPathTruthy(config: OpenClawConfig | undefined, pathStr: string): boolean {
|
export function isConfigPathTruthy(config: OpenClawConfig | undefined, pathStr: string): boolean {
|
||||||
const value = resolveConfigPath(config, pathStr);
|
const value = resolveConfigPath(config, pathStr);
|
||||||
if (value === undefined && pathStr in DEFAULT_CONFIG_VALUES) {
|
if (value === undefined && pathStr in DEFAULT_CONFIG_VALUES) {
|
||||||
return DEFAULT_CONFIG_VALUES[pathStr] === true;
|
return DEFAULT_CONFIG_VALUES[pathStr];
|
||||||
}
|
}
|
||||||
return isTruthy(value);
|
return isTruthy(value);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ function parseInstallSpec(input: unknown): SkillInstallSpec | undefined {
|
||||||
}
|
}
|
||||||
|
|
||||||
const spec: SkillInstallSpec = {
|
const spec: SkillInstallSpec = {
|
||||||
kind: kind as SkillInstallSpec["kind"],
|
kind: kind,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (typeof raw.id === "string") spec.id = raw.id;
|
if (typeof raw.id === "string") spec.id = raw.id;
|
||||||
|
|
@ -78,7 +78,7 @@ export function resolveOpenClawMetadata(
|
||||||
const raw = getFrontmatterValue(frontmatter, "metadata");
|
const raw = getFrontmatterValue(frontmatter, "metadata");
|
||||||
if (!raw) return undefined;
|
if (!raw) return undefined;
|
||||||
try {
|
try {
|
||||||
const parsed = JSON5.parse(raw) as Record<string, unknown>;
|
const parsed = JSON5.parse(raw);
|
||||||
if (!parsed || typeof parsed !== "object") return undefined;
|
if (!parsed || typeof parsed !== "object") return undefined;
|
||||||
const metadataRawCandidates = [MANIFEST_KEY, ...LEGACY_MANIFEST_KEYS];
|
const metadataRawCandidates = [MANIFEST_KEY, ...LEGACY_MANIFEST_KEYS];
|
||||||
let metadataRaw: unknown;
|
let metadataRaw: unknown;
|
||||||
|
|
|
||||||
|
|
@ -327,19 +327,14 @@ export async function runSubagentAnnounceFlow(params: {
|
||||||
let outcome: SubagentRunOutcome | undefined = params.outcome;
|
let outcome: SubagentRunOutcome | undefined = params.outcome;
|
||||||
if (!reply && params.waitForCompletion !== false) {
|
if (!reply && params.waitForCompletion !== false) {
|
||||||
const waitMs = Math.min(params.timeoutMs, 60_000);
|
const waitMs = Math.min(params.timeoutMs, 60_000);
|
||||||
const wait = (await callGateway({
|
const wait = await callGateway({
|
||||||
method: "agent.wait",
|
method: "agent.wait",
|
||||||
params: {
|
params: {
|
||||||
runId: params.childRunId,
|
runId: params.childRunId,
|
||||||
timeoutMs: waitMs,
|
timeoutMs: waitMs,
|
||||||
},
|
},
|
||||||
timeoutMs: waitMs + 2000,
|
timeoutMs: waitMs + 2000,
|
||||||
})) as {
|
});
|
||||||
status?: string;
|
|
||||||
error?: string;
|
|
||||||
startedAt?: number;
|
|
||||||
endedAt?: number;
|
|
||||||
};
|
|
||||||
if (wait?.status === "timeout") {
|
if (wait?.status === "timeout") {
|
||||||
outcome = { status: "timeout" };
|
outcome = { status: "timeout" };
|
||||||
} else if (wait?.status === "error") {
|
} else if (wait?.status === "error") {
|
||||||
|
|
|
||||||
|
|
@ -170,8 +170,7 @@ function ensureListener() {
|
||||||
}
|
}
|
||||||
const phase = evt.data?.phase;
|
const phase = evt.data?.phase;
|
||||||
if (phase === "start") {
|
if (phase === "start") {
|
||||||
const startedAt =
|
const startedAt = typeof evt.data?.startedAt === "number" ? evt.data.startedAt : undefined;
|
||||||
typeof evt.data?.startedAt === "number" ? (evt.data.startedAt as number) : undefined;
|
|
||||||
if (startedAt) {
|
if (startedAt) {
|
||||||
entry.startedAt = startedAt;
|
entry.startedAt = startedAt;
|
||||||
persistSubagentRuns();
|
persistSubagentRuns();
|
||||||
|
|
@ -179,11 +178,10 @@ function ensureListener() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (phase !== "end" && phase !== "error") return;
|
if (phase !== "end" && phase !== "error") return;
|
||||||
const endedAt =
|
const endedAt = typeof evt.data?.endedAt === "number" ? evt.data.endedAt : Date.now();
|
||||||
typeof evt.data?.endedAt === "number" ? (evt.data.endedAt as number) : Date.now();
|
|
||||||
entry.endedAt = endedAt;
|
entry.endedAt = endedAt;
|
||||||
if (phase === "error") {
|
if (phase === "error") {
|
||||||
const error = typeof evt.data?.error === "string" ? (evt.data.error as string) : undefined;
|
const error = typeof evt.data?.error === "string" ? evt.data.error : undefined;
|
||||||
entry.outcome = { status: "error", error };
|
entry.outcome = { status: "error", error };
|
||||||
} else {
|
} else {
|
||||||
entry.outcome = { status: "ok" };
|
entry.outcome = { status: "ok" };
|
||||||
|
|
@ -284,14 +282,14 @@ export function registerSubagentRun(params: {
|
||||||
async function waitForSubagentCompletion(runId: string, waitTimeoutMs: number) {
|
async function waitForSubagentCompletion(runId: string, waitTimeoutMs: number) {
|
||||||
try {
|
try {
|
||||||
const timeoutMs = Math.max(1, Math.floor(waitTimeoutMs));
|
const timeoutMs = Math.max(1, Math.floor(waitTimeoutMs));
|
||||||
const wait = (await callGateway({
|
const wait = await callGateway({
|
||||||
method: "agent.wait",
|
method: "agent.wait",
|
||||||
params: {
|
params: {
|
||||||
runId,
|
runId,
|
||||||
timeoutMs,
|
timeoutMs,
|
||||||
},
|
},
|
||||||
timeoutMs: timeoutMs + 10_000,
|
timeoutMs: timeoutMs + 10_000,
|
||||||
})) as { status?: string; startedAt?: number; endedAt?: number; error?: string };
|
});
|
||||||
if (wait?.status !== "ok" && wait?.status !== "error") return;
|
if (wait?.status !== "ok" && wait?.status !== "error") return;
|
||||||
const entry = subagentRuns.get(runId);
|
const entry = subagentRuns.get(runId);
|
||||||
if (!entry) return;
|
if (!entry) return;
|
||||||
|
|
|
||||||
|
|
@ -264,7 +264,7 @@ export function buildAgentSystemPrompt(params: {
|
||||||
const name = resolveToolName(tool);
|
const name = resolveToolName(tool);
|
||||||
return summary ? `- ${name}: ${summary}` : `- ${name}`;
|
return summary ? `- ${name}: ${summary}` : `- ${name}`;
|
||||||
});
|
});
|
||||||
for (const tool of extraTools.sort()) {
|
for (const tool of extraTools.toSorted()) {
|
||||||
const summary = coreToolSummaries[tool] ?? externalToolSummaries.get(tool);
|
const summary = coreToolSummaries[tool] ?? externalToolSummaries.get(tool);
|
||||||
const name = resolveToolName(tool);
|
const name = resolveToolName(tool);
|
||||||
toolLines.push(summary ? `- ${name}: ${summary}` : `- ${name}`);
|
toolLines.push(summary ? `- ${name}: ${summary}` : `- ${name}`);
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ async function resizeImageBase64IfNeeded(params: {
|
||||||
const sideGrid = [sideStart, 1800, 1600, 1400, 1200, 1000, 800]
|
const sideGrid = [sideStart, 1800, 1600, 1400, 1200, 1000, 800]
|
||||||
.map((v) => Math.min(params.maxDimensionPx, v))
|
.map((v) => Math.min(params.maxDimensionPx, v))
|
||||||
.filter((v, i, arr) => v > 0 && arr.indexOf(v) === i)
|
.filter((v, i, arr) => v > 0 && arr.indexOf(v) === i)
|
||||||
.sort((a, b) => b - a);
|
.toSorted((a, b) => b - a);
|
||||||
|
|
||||||
let smallest: { buffer: Buffer; size: number } | null = null;
|
let smallest: { buffer: Buffer; size: number } | null = null;
|
||||||
for (const side of sideGrid) {
|
for (const side of sideGrid) {
|
||||||
|
|
@ -191,7 +191,7 @@ export async function sanitizeImageBlocks(
|
||||||
): Promise<{ images: ImageContent[]; dropped: number }> {
|
): Promise<{ images: ImageContent[]; dropped: number }> {
|
||||||
if (images.length === 0) return { images, dropped: 0 };
|
if (images.length === 0) return { images, dropped: 0 };
|
||||||
const sanitized = await sanitizeContentBlocksImages(images as ToolContentBlock[], label, opts);
|
const sanitized = await sanitizeContentBlocksImages(images as ToolContentBlock[], label, opts);
|
||||||
const next = sanitized.filter(isImageBlock) as ImageContent[];
|
const next = sanitized.filter(isImageBlock);
|
||||||
return { images: next, dropped: Math.max(0, images.length - next.length) };
|
return { images: next, dropped: Math.max(0, images.length - next.length) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,10 @@ export async function readLatestAssistantReply(params: {
|
||||||
sessionKey: string;
|
sessionKey: string;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
}): Promise<string | undefined> {
|
}): Promise<string | undefined> {
|
||||||
const history = (await callGateway({
|
const history = await callGateway({
|
||||||
method: "chat.history",
|
method: "chat.history",
|
||||||
params: { sessionKey: params.sessionKey, limit: params.limit ?? 50 },
|
params: { sessionKey: params.sessionKey, limit: params.limit ?? 50 },
|
||||||
})) as { messages?: unknown[] };
|
});
|
||||||
const filtered = stripToolMessages(Array.isArray(history?.messages) ? history.messages : []);
|
const filtered = stripToolMessages(Array.isArray(history?.messages) ? history.messages : []);
|
||||||
const last = filtered.length > 0 ? filtered[filtered.length - 1] : undefined;
|
const last = filtered.length > 0 ? filtered[filtered.length - 1] : undefined;
|
||||||
return last ? extractAssistantText(last) : undefined;
|
return last ? extractAssistantText(last) : undefined;
|
||||||
|
|
@ -27,7 +27,7 @@ export async function runAgentStep(params: {
|
||||||
lane?: string;
|
lane?: string;
|
||||||
}): Promise<string | undefined> {
|
}): Promise<string | undefined> {
|
||||||
const stepIdem = crypto.randomUUID();
|
const stepIdem = crypto.randomUUID();
|
||||||
const response = (await callGateway({
|
const response = await callGateway({
|
||||||
method: "agent",
|
method: "agent",
|
||||||
params: {
|
params: {
|
||||||
message: params.message,
|
message: params.message,
|
||||||
|
|
@ -39,19 +39,19 @@ export async function runAgentStep(params: {
|
||||||
extraSystemPrompt: params.extraSystemPrompt,
|
extraSystemPrompt: params.extraSystemPrompt,
|
||||||
},
|
},
|
||||||
timeoutMs: 10_000,
|
timeoutMs: 10_000,
|
||||||
})) as { runId?: string; acceptedAt?: number };
|
});
|
||||||
|
|
||||||
const stepRunId = typeof response?.runId === "string" && response.runId ? response.runId : "";
|
const stepRunId = typeof response?.runId === "string" && response.runId ? response.runId : "";
|
||||||
const resolvedRunId = stepRunId || stepIdem;
|
const resolvedRunId = stepRunId || stepIdem;
|
||||||
const stepWaitMs = Math.min(params.timeoutMs, 60_000);
|
const stepWaitMs = Math.min(params.timeoutMs, 60_000);
|
||||||
const wait = (await callGateway({
|
const wait = await callGateway({
|
||||||
method: "agent.wait",
|
method: "agent.wait",
|
||||||
params: {
|
params: {
|
||||||
runId: resolvedRunId,
|
runId: resolvedRunId,
|
||||||
timeoutMs: stepWaitMs,
|
timeoutMs: stepWaitMs,
|
||||||
},
|
},
|
||||||
timeoutMs: stepWaitMs + 2000,
|
timeoutMs: stepWaitMs + 2000,
|
||||||
})) as { status?: string };
|
});
|
||||||
if (wait?.status !== "ok") return undefined;
|
if (wait?.status !== "ok") return undefined;
|
||||||
return await readLatestAssistantReply({ sessionKey: params.sessionKey });
|
return await readLatestAssistantReply({ sessionKey: params.sessionKey });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,9 @@ export function createAgentsListTool(opts?: {
|
||||||
}
|
}
|
||||||
|
|
||||||
const all = Array.from(allowed);
|
const all = Array.from(allowed);
|
||||||
const rest = all.filter((id) => id !== requesterAgentId).sort((a, b) => a.localeCompare(b));
|
const rest = all
|
||||||
|
.filter((id) => id !== requesterAgentId)
|
||||||
|
.toSorted((a, b) => a.localeCompare(b));
|
||||||
const ordered = [requesterAgentId, ...rest];
|
const ordered = [requesterAgentId, ...rest];
|
||||||
const agents: AgentListEntry[] = ordered.map((id) => ({
|
const agents: AgentListEntry[] = ordered.map((id) => ({
|
||||||
id,
|
id,
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ async function resolveBrowserNodeTarget(params: {
|
||||||
|
|
||||||
if (params.target === "node") {
|
if (params.target === "node") {
|
||||||
if (browserNodes.length === 1) {
|
if (browserNodes.length === 1) {
|
||||||
const node = browserNodes[0]!;
|
const node = browserNodes[0];
|
||||||
return { nodeId: node.nodeId, label: node.displayName ?? node.remoteIp ?? node.nodeId };
|
return { nodeId: node.nodeId, label: node.displayName ?? node.remoteIp ?? node.nodeId };
|
||||||
}
|
}
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|
@ -104,7 +104,7 @@ async function resolveBrowserNodeTarget(params: {
|
||||||
if (mode === "manual") return null;
|
if (mode === "manual") return null;
|
||||||
|
|
||||||
if (browserNodes.length === 1) {
|
if (browserNodes.length === 1) {
|
||||||
const node = browserNodes[0]!;
|
const node = browserNodes[0];
|
||||||
return { nodeId: node.nodeId, label: node.displayName ?? node.remoteIp ?? node.nodeId };
|
return { nodeId: node.nodeId, label: node.displayName ?? node.remoteIp ?? node.nodeId };
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -123,7 +123,7 @@ async function callBrowserProxy(params: {
|
||||||
typeof params.timeoutMs === "number" && Number.isFinite(params.timeoutMs)
|
typeof params.timeoutMs === "number" && Number.isFinite(params.timeoutMs)
|
||||||
? Math.max(1, Math.floor(params.timeoutMs))
|
? Math.max(1, Math.floor(params.timeoutMs))
|
||||||
: DEFAULT_BROWSER_PROXY_TIMEOUT_MS;
|
: DEFAULT_BROWSER_PROXY_TIMEOUT_MS;
|
||||||
const payload = (await callGatewayTool(
|
const payload = await callGatewayTool(
|
||||||
"node.invoke",
|
"node.invoke",
|
||||||
{ timeoutMs: gatewayTimeoutMs },
|
{ timeoutMs: gatewayTimeoutMs },
|
||||||
{
|
{
|
||||||
|
|
@ -139,11 +139,7 @@ async function callBrowserProxy(params: {
|
||||||
},
|
},
|
||||||
idempotencyKey: crypto.randomUUID(),
|
idempotencyKey: crypto.randomUUID(),
|
||||||
},
|
},
|
||||||
)) as {
|
);
|
||||||
ok?: boolean;
|
|
||||||
payload?: BrowserProxyResult;
|
|
||||||
payloadJSON?: string | null;
|
|
||||||
};
|
|
||||||
const parsed =
|
const parsed =
|
||||||
payload?.payload ??
|
payload?.payload ??
|
||||||
(typeof payload?.payloadJSON === "string" && payload.payloadJSON
|
(typeof payload?.payloadJSON === "string" && payload.payloadJSON
|
||||||
|
|
@ -414,7 +410,7 @@ export function createBrowserTool(opts?: {
|
||||||
const snapshotDefaults = loadConfig().browser?.snapshotDefaults;
|
const snapshotDefaults = loadConfig().browser?.snapshotDefaults;
|
||||||
const format =
|
const format =
|
||||||
params.snapshotFormat === "ai" || params.snapshotFormat === "aria"
|
params.snapshotFormat === "ai" || params.snapshotFormat === "aria"
|
||||||
? (params.snapshotFormat as "ai" | "aria")
|
? params.snapshotFormat
|
||||||
: "ai";
|
: "ai";
|
||||||
const mode =
|
const mode =
|
||||||
params.mode === "efficient"
|
params.mode === "efficient"
|
||||||
|
|
@ -697,10 +693,12 @@ export function createBrowserTool(opts?: {
|
||||||
if (!tabs.length) {
|
if (!tabs.length) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"No Chrome tabs are attached via the OpenClaw Browser Relay extension. Click the toolbar icon on the tab you want to control (badge ON), then retry.",
|
"No Chrome tabs are attached via the OpenClaw Browser Relay extension. Click the toolbar icon on the tab you want to control (badge ON), then retry.",
|
||||||
|
{ cause: err },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Chrome tab not found (stale targetId?). Run action=tabs profile="chrome" and use one of the returned targetIds.`,
|
`Chrome tab not found (stale targetId?). Run action=tabs profile="chrome" and use one of the returned targetIds.`,
|
||||||
|
{ cause: err },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
|
|
|
||||||
|
|
@ -103,10 +103,10 @@ async function buildReminderContextLines(params: {
|
||||||
const { mainKey, alias } = resolveMainSessionAlias(cfg);
|
const { mainKey, alias } = resolveMainSessionAlias(cfg);
|
||||||
const resolvedKey = resolveInternalSessionKey({ key: sessionKey, alias, mainKey });
|
const resolvedKey = resolveInternalSessionKey({ key: sessionKey, alias, mainKey });
|
||||||
try {
|
try {
|
||||||
const res = (await callGatewayTool("chat.history", params.gatewayOpts, {
|
const res = await callGatewayTool("chat.history", params.gatewayOpts, {
|
||||||
sessionKey: resolvedKey,
|
sessionKey: resolvedKey,
|
||||||
limit: maxMessages,
|
limit: maxMessages,
|
||||||
})) as { messages?: unknown[] };
|
});
|
||||||
const messages = Array.isArray(res?.messages) ? res.messages : [];
|
const messages = Array.isArray(res?.messages) ? res.messages : [];
|
||||||
const parsed = messages
|
const parsed = messages
|
||||||
.map((msg) => extractMessageText(msg as ChatMessage))
|
.map((msg) => extractMessageText(msg as ChatMessage))
|
||||||
|
|
|
||||||
|
|
@ -267,10 +267,10 @@ async function runImagePrompt(params: {
|
||||||
}
|
}
|
||||||
|
|
||||||
const context = buildImageContext(params.prompt, params.base64, params.mimeType);
|
const context = buildImageContext(params.prompt, params.base64, params.mimeType);
|
||||||
const message = (await complete(model, context, {
|
const message = await complete(model, context, {
|
||||||
apiKey,
|
apiKey,
|
||||||
maxTokens: 512,
|
maxTokens: 512,
|
||||||
})) as AssistantMessage;
|
});
|
||||||
const text = coerceImageAssistantText({
|
const text = coerceImageAssistantText({
|
||||||
message,
|
message,
|
||||||
provider: model.provider,
|
provider: model.provider,
|
||||||
|
|
|
||||||
|
|
@ -307,7 +307,7 @@ function buildMessageToolDescription(options?: {
|
||||||
if (channelActions.length > 0) {
|
if (channelActions.length > 0) {
|
||||||
// Always include "send" as a base action
|
// Always include "send" as a base action
|
||||||
const allActions = new Set(["send", ...channelActions]);
|
const allActions = new Set(["send", ...channelActions]);
|
||||||
const actionList = Array.from(allActions).sort().join(", ");
|
const actionList = Array.from(allActions).toSorted().join(", ");
|
||||||
return `${baseDescription} Current channel (${options.currentChannel}) supports: ${actionList}.`;
|
return `${baseDescription} Current channel (${options.currentChannel}) supports: ${actionList}.`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -199,7 +199,7 @@ export function createNodesTool(options?: {
|
||||||
const details: Array<Record<string, unknown>> = [];
|
const details: Array<Record<string, unknown>> = [];
|
||||||
|
|
||||||
for (const facing of facings) {
|
for (const facing of facings) {
|
||||||
const raw = (await callGatewayTool("node.invoke", gatewayOpts, {
|
const raw = await callGatewayTool("node.invoke", gatewayOpts, {
|
||||||
nodeId,
|
nodeId,
|
||||||
command: "camera.snap",
|
command: "camera.snap",
|
||||||
params: {
|
params: {
|
||||||
|
|
@ -211,7 +211,7 @@ export function createNodesTool(options?: {
|
||||||
deviceId,
|
deviceId,
|
||||||
},
|
},
|
||||||
idempotencyKey: crypto.randomUUID(),
|
idempotencyKey: crypto.randomUUID(),
|
||||||
})) as { payload?: unknown };
|
});
|
||||||
const payload = parseCameraSnapPayload(raw?.payload);
|
const payload = parseCameraSnapPayload(raw?.payload);
|
||||||
const normalizedFormat = payload.format.toLowerCase();
|
const normalizedFormat = payload.format.toLowerCase();
|
||||||
if (
|
if (
|
||||||
|
|
@ -250,12 +250,12 @@ export function createNodesTool(options?: {
|
||||||
case "camera_list": {
|
case "camera_list": {
|
||||||
const node = readStringParam(params, "node", { required: true });
|
const node = readStringParam(params, "node", { required: true });
|
||||||
const nodeId = await resolveNodeId(gatewayOpts, node);
|
const nodeId = await resolveNodeId(gatewayOpts, node);
|
||||||
const raw = (await callGatewayTool("node.invoke", gatewayOpts, {
|
const raw = await callGatewayTool("node.invoke", gatewayOpts, {
|
||||||
nodeId,
|
nodeId,
|
||||||
command: "camera.list",
|
command: "camera.list",
|
||||||
params: {},
|
params: {},
|
||||||
idempotencyKey: crypto.randomUUID(),
|
idempotencyKey: crypto.randomUUID(),
|
||||||
})) as { payload?: unknown };
|
});
|
||||||
const payload =
|
const payload =
|
||||||
raw && typeof raw.payload === "object" && raw.payload !== null ? raw.payload : {};
|
raw && typeof raw.payload === "object" && raw.payload !== null ? raw.payload : {};
|
||||||
return jsonResult(payload);
|
return jsonResult(payload);
|
||||||
|
|
@ -280,7 +280,7 @@ export function createNodesTool(options?: {
|
||||||
typeof params.deviceId === "string" && params.deviceId.trim()
|
typeof params.deviceId === "string" && params.deviceId.trim()
|
||||||
? params.deviceId.trim()
|
? params.deviceId.trim()
|
||||||
: undefined;
|
: undefined;
|
||||||
const raw = (await callGatewayTool("node.invoke", gatewayOpts, {
|
const raw = await callGatewayTool("node.invoke", gatewayOpts, {
|
||||||
nodeId,
|
nodeId,
|
||||||
command: "camera.clip",
|
command: "camera.clip",
|
||||||
params: {
|
params: {
|
||||||
|
|
@ -291,7 +291,7 @@ export function createNodesTool(options?: {
|
||||||
deviceId,
|
deviceId,
|
||||||
},
|
},
|
||||||
idempotencyKey: crypto.randomUUID(),
|
idempotencyKey: crypto.randomUUID(),
|
||||||
})) as { payload?: unknown };
|
});
|
||||||
const payload = parseCameraClipPayload(raw?.payload);
|
const payload = parseCameraClipPayload(raw?.payload);
|
||||||
const filePath = cameraTempPath({
|
const filePath = cameraTempPath({
|
||||||
kind: "clip",
|
kind: "clip",
|
||||||
|
|
@ -326,7 +326,7 @@ export function createNodesTool(options?: {
|
||||||
: 0;
|
: 0;
|
||||||
const includeAudio =
|
const includeAudio =
|
||||||
typeof params.includeAudio === "boolean" ? params.includeAudio : true;
|
typeof params.includeAudio === "boolean" ? params.includeAudio : true;
|
||||||
const raw = (await callGatewayTool("node.invoke", gatewayOpts, {
|
const raw = await callGatewayTool("node.invoke", gatewayOpts, {
|
||||||
nodeId,
|
nodeId,
|
||||||
command: "screen.record",
|
command: "screen.record",
|
||||||
params: {
|
params: {
|
||||||
|
|
@ -337,7 +337,7 @@ export function createNodesTool(options?: {
|
||||||
includeAudio,
|
includeAudio,
|
||||||
},
|
},
|
||||||
idempotencyKey: crypto.randomUUID(),
|
idempotencyKey: crypto.randomUUID(),
|
||||||
})) as { payload?: unknown };
|
});
|
||||||
const payload = parseScreenRecordPayload(raw?.payload);
|
const payload = parseScreenRecordPayload(raw?.payload);
|
||||||
const filePath =
|
const filePath =
|
||||||
typeof params.outPath === "string" && params.outPath.trim()
|
typeof params.outPath === "string" && params.outPath.trim()
|
||||||
|
|
@ -373,7 +373,7 @@ export function createNodesTool(options?: {
|
||||||
Number.isFinite(params.locationTimeoutMs)
|
Number.isFinite(params.locationTimeoutMs)
|
||||||
? params.locationTimeoutMs
|
? params.locationTimeoutMs
|
||||||
: undefined;
|
: undefined;
|
||||||
const raw = (await callGatewayTool("node.invoke", gatewayOpts, {
|
const raw = await callGatewayTool("node.invoke", gatewayOpts, {
|
||||||
nodeId,
|
nodeId,
|
||||||
command: "location.get",
|
command: "location.get",
|
||||||
params: {
|
params: {
|
||||||
|
|
@ -382,7 +382,7 @@ export function createNodesTool(options?: {
|
||||||
timeoutMs: locationTimeoutMs,
|
timeoutMs: locationTimeoutMs,
|
||||||
},
|
},
|
||||||
idempotencyKey: crypto.randomUUID(),
|
idempotencyKey: crypto.randomUUID(),
|
||||||
})) as { payload?: unknown };
|
});
|
||||||
return jsonResult(raw?.payload ?? {});
|
return jsonResult(raw?.payload ?? {});
|
||||||
}
|
}
|
||||||
case "run": {
|
case "run": {
|
||||||
|
|
@ -423,7 +423,7 @@ export function createNodesTool(options?: {
|
||||||
typeof params.needsScreenRecording === "boolean"
|
typeof params.needsScreenRecording === "boolean"
|
||||||
? params.needsScreenRecording
|
? params.needsScreenRecording
|
||||||
: undefined;
|
: undefined;
|
||||||
const raw = (await callGatewayTool("node.invoke", gatewayOpts, {
|
const raw = await callGatewayTool("node.invoke", gatewayOpts, {
|
||||||
nodeId,
|
nodeId,
|
||||||
command: "system.run",
|
command: "system.run",
|
||||||
params: {
|
params: {
|
||||||
|
|
@ -437,7 +437,7 @@ export function createNodesTool(options?: {
|
||||||
},
|
},
|
||||||
timeoutMs: invokeTimeoutMs,
|
timeoutMs: invokeTimeoutMs,
|
||||||
idempotencyKey: crypto.randomUUID(),
|
idempotencyKey: crypto.randomUUID(),
|
||||||
})) as { payload?: unknown };
|
});
|
||||||
return jsonResult(raw?.payload ?? {});
|
return jsonResult(raw?.payload ?? {});
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|
@ -454,6 +454,7 @@ export function createNodesTool(options?: {
|
||||||
const message = err instanceof Error ? err.message : String(err);
|
const message = err instanceof Error ? err.message : String(err);
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`agent=${agentLabel} node=${nodeLabel} gateway=${gatewayLabel} action=${action}: ${message}`,
|
`agent=${agentLabel} node=${nodeLabel} gateway=${gatewayLabel} action=${action}: ${message}`,
|
||||||
|
{ cause: err },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -71,10 +71,10 @@ function normalizeNodeKey(value: string) {
|
||||||
|
|
||||||
async function loadNodes(opts: GatewayCallOptions): Promise<NodeListNode[]> {
|
async function loadNodes(opts: GatewayCallOptions): Promise<NodeListNode[]> {
|
||||||
try {
|
try {
|
||||||
const res = (await callGatewayTool("node.list", opts, {})) as unknown;
|
const res = await callGatewayTool("node.list", opts, {});
|
||||||
return parseNodeList(res);
|
return parseNodeList(res);
|
||||||
} catch {
|
} catch {
|
||||||
const res = (await callGatewayTool("node.pair.list", opts, {})) as unknown;
|
const res = await callGatewayTool("node.pair.list", opts, {});
|
||||||
const { paired } = parsePairingList(res);
|
const { paired } = parsePairingList(res);
|
||||||
return paired.map((n) => ({
|
return paired.map((n) => ({
|
||||||
nodeId: n.nodeId,
|
nodeId: n.nodeId,
|
||||||
|
|
|
||||||
|
|
@ -20,14 +20,14 @@ export async function resolveAnnounceTarget(params: {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const list = (await callGateway({
|
const list = await callGateway({
|
||||||
method: "sessions.list",
|
method: "sessions.list",
|
||||||
params: {
|
params: {
|
||||||
includeGlobal: true,
|
includeGlobal: true,
|
||||||
includeUnknown: true,
|
includeUnknown: true,
|
||||||
limit: 200,
|
limit: 200,
|
||||||
},
|
},
|
||||||
})) as { sessions?: Array<Record<string, unknown>> };
|
});
|
||||||
const sessions = Array.isArray(list?.sessions) ? list.sessions : [];
|
const sessions = Array.isArray(list?.sessions) ? list.sessions : [];
|
||||||
const match =
|
const match =
|
||||||
sessions.find((entry) => entry?.key === params.sessionKey) ??
|
sessions.find((entry) => entry?.key === params.sessionKey) ??
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ async function resolveSessionKeyFromSessionId(params: {
|
||||||
}): Promise<SessionReferenceResolution> {
|
}): Promise<SessionReferenceResolution> {
|
||||||
try {
|
try {
|
||||||
// Resolve via gateway so we respect store routing and visibility rules.
|
// Resolve via gateway so we respect store routing and visibility rules.
|
||||||
const result = (await callGateway({
|
const result = await callGateway({
|
||||||
method: "sessions.resolve",
|
method: "sessions.resolve",
|
||||||
params: {
|
params: {
|
||||||
sessionId: params.sessionId,
|
sessionId: params.sessionId,
|
||||||
|
|
@ -143,7 +143,7 @@ async function resolveSessionKeyFromSessionId(params: {
|
||||||
includeGlobal: !params.restrictToSpawned,
|
includeGlobal: !params.restrictToSpawned,
|
||||||
includeUnknown: !params.restrictToSpawned,
|
includeUnknown: !params.restrictToSpawned,
|
||||||
},
|
},
|
||||||
})) as { key?: unknown };
|
});
|
||||||
const key = typeof result?.key === "string" ? result.key.trim() : "";
|
const key = typeof result?.key === "string" ? result.key.trim() : "";
|
||||||
if (!key) {
|
if (!key) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|
@ -188,13 +188,13 @@ async function resolveSessionKeyFromKey(params: {
|
||||||
}): Promise<SessionReferenceResolution | null> {
|
}): Promise<SessionReferenceResolution | null> {
|
||||||
try {
|
try {
|
||||||
// Try key-based resolution first so non-standard keys keep working.
|
// Try key-based resolution first so non-standard keys keep working.
|
||||||
const result = (await callGateway({
|
const result = await callGateway({
|
||||||
method: "sessions.resolve",
|
method: "sessions.resolve",
|
||||||
params: {
|
params: {
|
||||||
key: params.key,
|
key: params.key,
|
||||||
spawnedBy: params.restrictToSpawned ? params.requesterInternalKey : undefined,
|
spawnedBy: params.restrictToSpawned ? params.requesterInternalKey : undefined,
|
||||||
},
|
},
|
||||||
})) as { key?: unknown };
|
});
|
||||||
const key = typeof result?.key === "string" ? result.key.trim() : "";
|
const key = typeof result?.key === "string" ? result.key.trim() : "";
|
||||||
if (!key) return null;
|
if (!key) return null;
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ async function isSpawnedSessionAllowed(params: {
|
||||||
targetSessionKey: string;
|
targetSessionKey: string;
|
||||||
}): Promise<boolean> {
|
}): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const list = (await callGateway({
|
const list = await callGateway({
|
||||||
method: "sessions.list",
|
method: "sessions.list",
|
||||||
params: {
|
params: {
|
||||||
includeGlobal: false,
|
includeGlobal: false,
|
||||||
|
|
@ -36,7 +36,7 @@ async function isSpawnedSessionAllowed(params: {
|
||||||
limit: 500,
|
limit: 500,
|
||||||
spawnedBy: params.requesterSessionKey,
|
spawnedBy: params.requesterSessionKey,
|
||||||
},
|
},
|
||||||
})) as { sessions?: Array<Record<string, unknown>> };
|
});
|
||||||
const sessions = Array.isArray(list?.sessions) ? list.sessions : [];
|
const sessions = Array.isArray(list?.sessions) ? list.sessions : [];
|
||||||
return sessions.some((entry) => entry?.key === params.targetSessionKey);
|
return sessions.some((entry) => entry?.key === params.targetSessionKey);
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -126,10 +126,10 @@ export function createSessionsHistoryTool(opts?: {
|
||||||
? Math.max(1, Math.floor(params.limit))
|
? Math.max(1, Math.floor(params.limit))
|
||||||
: undefined;
|
: undefined;
|
||||||
const includeTools = Boolean(params.includeTools);
|
const includeTools = Boolean(params.includeTools);
|
||||||
const result = (await callGateway({
|
const result = await callGateway({
|
||||||
method: "chat.history",
|
method: "chat.history",
|
||||||
params: { sessionKey: resolvedKey, limit },
|
params: { sessionKey: resolvedKey, limit },
|
||||||
})) as { messages?: unknown[] };
|
});
|
||||||
const rawMessages = Array.isArray(result?.messages) ? result.messages : [];
|
const rawMessages = Array.isArray(result?.messages) ? result.messages : [];
|
||||||
const messages = includeTools ? rawMessages : stripToolMessages(rawMessages);
|
const messages = includeTools ? rawMessages : stripToolMessages(rawMessages);
|
||||||
return jsonResult({
|
return jsonResult({
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ export function createSessionsListTool(opts?: {
|
||||||
: 0;
|
: 0;
|
||||||
const messageLimit = Math.min(messageLimitRaw, 20);
|
const messageLimit = Math.min(messageLimitRaw, 20);
|
||||||
|
|
||||||
const list = (await callGateway({
|
const list = await callGateway({
|
||||||
method: "sessions.list",
|
method: "sessions.list",
|
||||||
params: {
|
params: {
|
||||||
limit,
|
limit,
|
||||||
|
|
@ -88,10 +88,7 @@ export function createSessionsListTool(opts?: {
|
||||||
includeUnknown: !restrictToSpawned,
|
includeUnknown: !restrictToSpawned,
|
||||||
spawnedBy: restrictToSpawned ? requesterInternalKey : undefined,
|
spawnedBy: restrictToSpawned ? requesterInternalKey : undefined,
|
||||||
},
|
},
|
||||||
})) as {
|
});
|
||||||
path?: string;
|
|
||||||
sessions?: Array<Record<string, unknown>>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const sessions = Array.isArray(list?.sessions) ? list.sessions : [];
|
const sessions = Array.isArray(list?.sessions) ? list.sessions : [];
|
||||||
const storePath = typeof list?.path === "string" ? list.path : undefined;
|
const storePath = typeof list?.path === "string" ? list.path : undefined;
|
||||||
|
|
@ -187,10 +184,10 @@ export function createSessionsListTool(opts?: {
|
||||||
alias,
|
alias,
|
||||||
mainKey,
|
mainKey,
|
||||||
});
|
});
|
||||||
const history = (await callGateway({
|
const history = await callGateway({
|
||||||
method: "chat.history",
|
method: "chat.history",
|
||||||
params: { sessionKey: resolvedKey, limit: messageLimit },
|
params: { sessionKey: resolvedKey, limit: messageLimit },
|
||||||
})) as { messages?: unknown[] };
|
});
|
||||||
const rawMessages = Array.isArray(history?.messages) ? history.messages : [];
|
const rawMessages = Array.isArray(history?.messages) ? history.messages : [];
|
||||||
const filtered = stripToolMessages(rawMessages);
|
const filtered = stripToolMessages(rawMessages);
|
||||||
row.messages = filtered.length > messageLimit ? filtered.slice(-messageLimit) : filtered;
|
row.messages = filtered.length > messageLimit ? filtered.slice(-messageLimit) : filtered;
|
||||||
|
|
|
||||||
|
|
@ -33,14 +33,14 @@ export async function runSessionsSendA2AFlow(params: {
|
||||||
let latestReply = params.roundOneReply;
|
let latestReply = params.roundOneReply;
|
||||||
if (!primaryReply && params.waitRunId) {
|
if (!primaryReply && params.waitRunId) {
|
||||||
const waitMs = Math.min(params.announceTimeoutMs, 60_000);
|
const waitMs = Math.min(params.announceTimeoutMs, 60_000);
|
||||||
const wait = (await callGateway({
|
const wait = await callGateway({
|
||||||
method: "agent.wait",
|
method: "agent.wait",
|
||||||
params: {
|
params: {
|
||||||
runId: params.waitRunId,
|
runId: params.waitRunId,
|
||||||
timeoutMs: waitMs,
|
timeoutMs: waitMs,
|
||||||
},
|
},
|
||||||
timeoutMs: waitMs + 2000,
|
timeoutMs: waitMs + 2000,
|
||||||
})) as { status?: string };
|
});
|
||||||
if (wait?.status === "ok") {
|
if (wait?.status === "ok") {
|
||||||
primaryReply = await readLatestAssistantReply({
|
primaryReply = await readLatestAssistantReply({
|
||||||
sessionKey: params.targetSessionKey,
|
sessionKey: params.targetSessionKey,
|
||||||
|
|
|
||||||
|
|
@ -81,11 +81,11 @@ export function createSessionsSendTool(opts?: {
|
||||||
}
|
}
|
||||||
|
|
||||||
const listSessions = async (listParams: Record<string, unknown>) => {
|
const listSessions = async (listParams: Record<string, unknown>) => {
|
||||||
const result = (await callGateway({
|
const result = await callGateway({
|
||||||
method: "sessions.list",
|
method: "sessions.list",
|
||||||
params: listParams,
|
params: listParams,
|
||||||
timeoutMs: 10_000,
|
timeoutMs: 10_000,
|
||||||
})) as { sessions?: Array<Record<string, unknown>> };
|
});
|
||||||
return Array.isArray(result?.sessions) ? result.sessions : [];
|
return Array.isArray(result?.sessions) ? result.sessions : [];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -136,11 +136,11 @@ export function createSessionsSendTool(opts?: {
|
||||||
};
|
};
|
||||||
let resolvedKey = "";
|
let resolvedKey = "";
|
||||||
try {
|
try {
|
||||||
const resolved = (await callGateway({
|
const resolved = await callGateway({
|
||||||
method: "sessions.resolve",
|
method: "sessions.resolve",
|
||||||
params: resolveParams,
|
params: resolveParams,
|
||||||
timeoutMs: 10_000,
|
timeoutMs: 10_000,
|
||||||
})) as { key?: unknown };
|
});
|
||||||
resolvedKey = typeof resolved?.key === "string" ? resolved.key.trim() : "";
|
resolvedKey = typeof resolved?.key === "string" ? resolved.key.trim() : "";
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const msg = err instanceof Error ? err.message : String(err);
|
const msg = err instanceof Error ? err.message : String(err);
|
||||||
|
|
@ -283,11 +283,11 @@ export function createSessionsSendTool(opts?: {
|
||||||
|
|
||||||
if (timeoutSeconds === 0) {
|
if (timeoutSeconds === 0) {
|
||||||
try {
|
try {
|
||||||
const response = (await callGateway({
|
const response = await callGateway({
|
||||||
method: "agent",
|
method: "agent",
|
||||||
params: sendParams,
|
params: sendParams,
|
||||||
timeoutMs: 10_000,
|
timeoutMs: 10_000,
|
||||||
})) as { runId?: string; acceptedAt?: number };
|
});
|
||||||
if (typeof response?.runId === "string" && response.runId) {
|
if (typeof response?.runId === "string" && response.runId) {
|
||||||
runId = response.runId;
|
runId = response.runId;
|
||||||
}
|
}
|
||||||
|
|
@ -311,11 +311,11 @@ export function createSessionsSendTool(opts?: {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = (await callGateway({
|
const response = await callGateway({
|
||||||
method: "agent",
|
method: "agent",
|
||||||
params: sendParams,
|
params: sendParams,
|
||||||
timeoutMs: 10_000,
|
timeoutMs: 10_000,
|
||||||
})) as { runId?: string; acceptedAt?: number };
|
});
|
||||||
if (typeof response?.runId === "string" && response.runId) {
|
if (typeof response?.runId === "string" && response.runId) {
|
||||||
runId = response.runId;
|
runId = response.runId;
|
||||||
}
|
}
|
||||||
|
|
@ -333,14 +333,14 @@ export function createSessionsSendTool(opts?: {
|
||||||
let waitStatus: string | undefined;
|
let waitStatus: string | undefined;
|
||||||
let waitError: string | undefined;
|
let waitError: string | undefined;
|
||||||
try {
|
try {
|
||||||
const wait = (await callGateway({
|
const wait = await callGateway({
|
||||||
method: "agent.wait",
|
method: "agent.wait",
|
||||||
params: {
|
params: {
|
||||||
runId,
|
runId,
|
||||||
timeoutMs,
|
timeoutMs,
|
||||||
},
|
},
|
||||||
timeoutMs: timeoutMs + 2000,
|
timeoutMs: timeoutMs + 2000,
|
||||||
})) as { status?: string; error?: string };
|
});
|
||||||
waitStatus = typeof wait?.status === "string" ? wait.status : undefined;
|
waitStatus = typeof wait?.status === "string" ? wait.status : undefined;
|
||||||
waitError = typeof wait?.error === "string" ? wait.error : undefined;
|
waitError = typeof wait?.error === "string" ? wait.error : undefined;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -371,10 +371,10 @@ export function createSessionsSendTool(opts?: {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const history = (await callGateway({
|
const history = await callGateway({
|
||||||
method: "chat.history",
|
method: "chat.history",
|
||||||
params: { sessionKey: resolvedKey, limit: 50 },
|
params: { sessionKey: resolvedKey, limit: 50 },
|
||||||
})) as { messages?: unknown[] };
|
});
|
||||||
const filtered = stripToolMessages(Array.isArray(history?.messages) ? history.messages : []);
|
const filtered = stripToolMessages(Array.isArray(history?.messages) ? history.messages : []);
|
||||||
const last = filtered.length > 0 ? filtered[filtered.length - 1] : undefined;
|
const last = filtered.length > 0 ? filtered[filtered.length - 1] : undefined;
|
||||||
const reply = last ? extractAssistantText(last) : undefined;
|
const reply = last ? extractAssistantText(last) : undefined;
|
||||||
|
|
|
||||||
|
|
@ -84,9 +84,7 @@ export function createSessionsSpawnTool(opts?: {
|
||||||
const modelOverride = readStringParam(params, "model");
|
const modelOverride = readStringParam(params, "model");
|
||||||
const thinkingOverrideRaw = readStringParam(params, "thinking");
|
const thinkingOverrideRaw = readStringParam(params, "thinking");
|
||||||
const cleanup =
|
const cleanup =
|
||||||
params.cleanup === "keep" || params.cleanup === "delete"
|
params.cleanup === "keep" || params.cleanup === "delete" ? params.cleanup : "keep";
|
||||||
? (params.cleanup as "keep" | "delete")
|
|
||||||
: "keep";
|
|
||||||
const requesterOrigin = normalizeDeliveryContext({
|
const requesterOrigin = normalizeDeliveryContext({
|
||||||
channel: opts?.agentChannel,
|
channel: opts?.agentChannel,
|
||||||
accountId: opts?.agentAccountId,
|
accountId: opts?.agentAccountId,
|
||||||
|
|
@ -211,7 +209,7 @@ export function createSessionsSpawnTool(opts?: {
|
||||||
const childIdem = crypto.randomUUID();
|
const childIdem = crypto.randomUUID();
|
||||||
let childRunId: string = childIdem;
|
let childRunId: string = childIdem;
|
||||||
try {
|
try {
|
||||||
const response = (await callGateway({
|
const response = await callGateway({
|
||||||
method: "agent",
|
method: "agent",
|
||||||
params: {
|
params: {
|
||||||
message: task,
|
message: task,
|
||||||
|
|
@ -230,7 +228,7 @@ export function createSessionsSpawnTool(opts?: {
|
||||||
groupSpace: opts?.agentGroupSpace ?? undefined,
|
groupSpace: opts?.agentGroupSpace ?? undefined,
|
||||||
},
|
},
|
||||||
timeoutMs: 10_000,
|
timeoutMs: 10_000,
|
||||||
})) as { runId?: string };
|
});
|
||||||
if (typeof response?.runId === "string" && response.runId) {
|
if (typeof response?.runId === "string" && response.runId) {
|
||||||
childRunId = response.runId;
|
childRunId = response.runId;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ function formatZonedTimestamp(date: Date, timeZone?: string): string | undefined
|
||||||
const hh = pick("hour");
|
const hh = pick("hour");
|
||||||
const min = pick("minute");
|
const min = pick("minute");
|
||||||
const tz = [...parts]
|
const tz = [...parts]
|
||||||
.reverse()
|
.toReversed()
|
||||||
.find((part) => part.type === "timeZoneName")
|
.find((part) => part.type === "timeZoneName")
|
||||||
?.value?.trim();
|
?.value?.trim();
|
||||||
if (!yyyy || !mm || !dd || !hh || !min) return undefined;
|
if (!yyyy || !mm || !dd || !hh || !min) return undefined;
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ function formatListTop(
|
||||||
entries: Array<{ name: string; value: number }>,
|
entries: Array<{ name: string; value: number }>,
|
||||||
cap: number,
|
cap: number,
|
||||||
): { lines: string[]; omitted: number } {
|
): { lines: string[]; omitted: number } {
|
||||||
const sorted = [...entries].sort((a, b) => b.value - a.value);
|
const sorted = [...entries].toSorted((a, b) => b.value - a.value);
|
||||||
const top = sorted.slice(0, cap);
|
const top = sorted.slice(0, cap);
|
||||||
const omitted = Math.max(0, sorted.length - top.length);
|
const omitted = Math.max(0, sorted.length - top.length);
|
||||||
const lines = top.map((e) => `- ${e.name}: ${formatCharsAndTokens(e.value)}`);
|
const lines = top.map((e) => `- ${e.name}: ${formatCharsAndTokens(e.value)}`);
|
||||||
|
|
@ -263,7 +263,7 @@ export async function buildContextReply(params: HandleCommandsParams): Promise<R
|
||||||
);
|
);
|
||||||
const toolPropsLines = report.tools.entries
|
const toolPropsLines = report.tools.entries
|
||||||
.filter((t) => t.propertiesCount != null)
|
.filter((t) => t.propertiesCount != null)
|
||||||
.sort((a, b) => (b.propertiesCount ?? 0) - (a.propertiesCount ?? 0))
|
.toSorted((a, b) => (b.propertiesCount ?? 0) - (a.propertiesCount ?? 0))
|
||||||
.slice(0, 30)
|
.slice(0, 30)
|
||||||
.map((t) => `- ${t.name}: ${t.propertiesCount} params`);
|
.map((t) => `- ${t.name}: ${t.propertiesCount} params`);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,7 @@ export async function resolveModelsCommandReply(params: {
|
||||||
add(resolvedDefault.provider, resolvedDefault.model);
|
add(resolvedDefault.provider, resolvedDefault.model);
|
||||||
addModelConfigEntries();
|
addModelConfigEntries();
|
||||||
|
|
||||||
const providers = [...byProvider.keys()].sort();
|
const providers = [...byProvider.keys()].toSorted();
|
||||||
|
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
const lines: string[] = [
|
const lines: string[] = [
|
||||||
|
|
@ -181,7 +181,7 @@ export async function resolveModelsCommandReply(params: {
|
||||||
return { text: lines.join("\n") };
|
return { text: lines.join("\n") };
|
||||||
}
|
}
|
||||||
|
|
||||||
const models = [...(byProvider.get(provider) ?? new Set<string>())].sort();
|
const models = [...(byProvider.get(provider) ?? new Set<string>())].toSorted();
|
||||||
const total = models.length;
|
const total = models.length;
|
||||||
|
|
||||||
if (total === 0) {
|
if (total === 0) {
|
||||||
|
|
|
||||||
|
|
@ -289,7 +289,7 @@ export const handleStopCommand: CommandHandler = async (params, allowTextCommand
|
||||||
params.sessionStore[abortTarget.key] = abortTarget.entry;
|
params.sessionStore[abortTarget.key] = abortTarget.entry;
|
||||||
if (params.storePath) {
|
if (params.storePath) {
|
||||||
await updateSessionStore(params.storePath, (store) => {
|
await updateSessionStore(params.storePath, (store) => {
|
||||||
store[abortTarget.key] = abortTarget.entry as SessionEntry;
|
store[abortTarget.key] = abortTarget.entry;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (params.command.abortKey) {
|
} else if (params.command.abortKey) {
|
||||||
|
|
@ -336,7 +336,7 @@ export const handleAbortTrigger: CommandHandler = async (params, allowTextComman
|
||||||
params.sessionStore[abortTarget.key] = abortTarget.entry;
|
params.sessionStore[abortTarget.key] = abortTarget.entry;
|
||||||
if (params.storePath) {
|
if (params.storePath) {
|
||||||
await updateSessionStore(params.storePath, (store) => {
|
await updateSessionStore(params.storePath, (store) => {
|
||||||
store[abortTarget.key] = abortTarget.entry as SessionEntry;
|
store[abortTarget.key] = abortTarget.entry;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (params.command.abortKey) {
|
} else if (params.command.abortKey) {
|
||||||
|
|
|
||||||
|
|
@ -316,10 +316,10 @@ export const handleSubagentsCommand: CommandHandler = async (params, allowTextCo
|
||||||
reply: { text: `⚠️ ${resolved.error ?? "Unknown subagent."}` },
|
reply: { text: `⚠️ ${resolved.error ?? "Unknown subagent."}` },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const history = (await callGateway({
|
const history = await callGateway({
|
||||||
method: "chat.history",
|
method: "chat.history",
|
||||||
params: { sessionKey: resolved.entry.childSessionKey, limit },
|
params: { sessionKey: resolved.entry.childSessionKey, limit },
|
||||||
})) as { messages?: unknown[] };
|
});
|
||||||
const rawMessages = Array.isArray(history?.messages) ? history.messages : [];
|
const rawMessages = Array.isArray(history?.messages) ? history.messages : [];
|
||||||
const filtered = includeTools ? rawMessages : stripToolMessages(rawMessages);
|
const filtered = includeTools ? rawMessages : stripToolMessages(rawMessages);
|
||||||
const lines = formatLogLines(filtered as ChatMessage[]);
|
const lines = formatLogLines(filtered as ChatMessage[]);
|
||||||
|
|
@ -349,7 +349,7 @@ export const handleSubagentsCommand: CommandHandler = async (params, allowTextCo
|
||||||
const idempotencyKey = crypto.randomUUID();
|
const idempotencyKey = crypto.randomUUID();
|
||||||
let runId: string = idempotencyKey;
|
let runId: string = idempotencyKey;
|
||||||
try {
|
try {
|
||||||
const response = (await callGateway({
|
const response = await callGateway({
|
||||||
method: "agent",
|
method: "agent",
|
||||||
params: {
|
params: {
|
||||||
message,
|
message,
|
||||||
|
|
@ -360,7 +360,7 @@ export const handleSubagentsCommand: CommandHandler = async (params, allowTextCo
|
||||||
lane: AGENT_LANE_SUBAGENT,
|
lane: AGENT_LANE_SUBAGENT,
|
||||||
},
|
},
|
||||||
timeoutMs: 10_000,
|
timeoutMs: 10_000,
|
||||||
})) as { runId?: string };
|
});
|
||||||
if (response?.runId) runId = response.runId;
|
if (response?.runId) runId = response.runId;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const messageText =
|
const messageText =
|
||||||
|
|
@ -369,11 +369,11 @@ export const handleSubagentsCommand: CommandHandler = async (params, allowTextCo
|
||||||
}
|
}
|
||||||
|
|
||||||
const waitMs = 30_000;
|
const waitMs = 30_000;
|
||||||
const wait = (await callGateway({
|
const wait = await callGateway({
|
||||||
method: "agent.wait",
|
method: "agent.wait",
|
||||||
params: { runId, timeoutMs: waitMs },
|
params: { runId, timeoutMs: waitMs },
|
||||||
timeoutMs: waitMs + 2000,
|
timeoutMs: waitMs + 2000,
|
||||||
})) as { status?: string; error?: string };
|
});
|
||||||
if (wait?.status === "timeout") {
|
if (wait?.status === "timeout") {
|
||||||
return {
|
return {
|
||||||
shouldContinue: false,
|
shouldContinue: false,
|
||||||
|
|
@ -389,10 +389,10 @@ export const handleSubagentsCommand: CommandHandler = async (params, allowTextCo
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const history = (await callGateway({
|
const history = await callGateway({
|
||||||
method: "chat.history",
|
method: "chat.history",
|
||||||
params: { sessionKey: resolved.entry.childSessionKey, limit: 50 },
|
params: { sessionKey: resolved.entry.childSessionKey, limit: 50 },
|
||||||
})) as { messages?: unknown[] };
|
});
|
||||||
const filtered = stripToolMessages(Array.isArray(history?.messages) ? history.messages : []);
|
const filtered = stripToolMessages(Array.isArray(history?.messages) ? history.messages : []);
|
||||||
const last = filtered.length > 0 ? filtered[filtered.length - 1] : undefined;
|
const last = filtered.length > 0 ? filtered[filtered.length - 1] : undefined;
|
||||||
const replyText = last ? extractAssistantText(last) : undefined;
|
const replyText = last ? extractAssistantText(last) : undefined;
|
||||||
|
|
|
||||||
|
|
@ -54,8 +54,7 @@ function resolveExecDefaults(params: {
|
||||||
(agentExec?.ask as ExecAsk | undefined) ??
|
(agentExec?.ask as ExecAsk | undefined) ??
|
||||||
(globalExec?.ask as ExecAsk | undefined) ??
|
(globalExec?.ask as ExecAsk | undefined) ??
|
||||||
"on-miss",
|
"on-miss",
|
||||||
node:
|
node: params.sessionEntry?.execNode ?? agentExec?.node ?? globalExec?.node,
|
||||||
(params.sessionEntry?.execNode as string | undefined) ?? agentExec?.node ?? globalExec?.node,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -196,8 +196,8 @@ export async function applyInlineDirectiveOverrides(params: {
|
||||||
model,
|
model,
|
||||||
contextTokens,
|
contextTokens,
|
||||||
resolvedThinkLevel: resolvedDefaultThinkLevel,
|
resolvedThinkLevel: resolvedDefaultThinkLevel,
|
||||||
resolvedVerboseLevel: (currentVerboseLevel ?? "off") as VerboseLevel,
|
resolvedVerboseLevel: currentVerboseLevel ?? "off",
|
||||||
resolvedReasoningLevel: (currentReasoningLevel ?? "off") as ReasoningLevel,
|
resolvedReasoningLevel: currentReasoningLevel ?? "off",
|
||||||
resolvedElevatedLevel,
|
resolvedElevatedLevel,
|
||||||
resolveDefaultThinkingLevel: async () => resolvedDefaultThinkLevel,
|
resolveDefaultThinkingLevel: async () => resolvedDefaultThinkLevel,
|
||||||
isGroup,
|
isGroup,
|
||||||
|
|
|
||||||
|
|
@ -339,20 +339,20 @@ export async function resolveReplyDirectives(params: {
|
||||||
});
|
});
|
||||||
const defaultActivation = defaultGroupActivation(requireMention);
|
const defaultActivation = defaultGroupActivation(requireMention);
|
||||||
const resolvedThinkLevel =
|
const resolvedThinkLevel =
|
||||||
(directives.thinkLevel as ThinkLevel | undefined) ??
|
directives.thinkLevel ??
|
||||||
(sessionEntry?.thinkingLevel as ThinkLevel | undefined) ??
|
(sessionEntry?.thinkingLevel as ThinkLevel | undefined) ??
|
||||||
(agentCfg?.thinkingDefault as ThinkLevel | undefined);
|
(agentCfg?.thinkingDefault as ThinkLevel | undefined);
|
||||||
|
|
||||||
const resolvedVerboseLevel =
|
const resolvedVerboseLevel =
|
||||||
(directives.verboseLevel as VerboseLevel | undefined) ??
|
directives.verboseLevel ??
|
||||||
(sessionEntry?.verboseLevel as VerboseLevel | undefined) ??
|
(sessionEntry?.verboseLevel as VerboseLevel | undefined) ??
|
||||||
(agentCfg?.verboseDefault as VerboseLevel | undefined);
|
(agentCfg?.verboseDefault as VerboseLevel | undefined);
|
||||||
const resolvedReasoningLevel: ReasoningLevel =
|
const resolvedReasoningLevel: ReasoningLevel =
|
||||||
(directives.reasoningLevel as ReasoningLevel | undefined) ??
|
directives.reasoningLevel ??
|
||||||
(sessionEntry?.reasoningLevel as ReasoningLevel | undefined) ??
|
(sessionEntry?.reasoningLevel as ReasoningLevel | undefined) ??
|
||||||
"off";
|
"off";
|
||||||
const resolvedElevatedLevel = elevatedAllowed
|
const resolvedElevatedLevel = elevatedAllowed
|
||||||
? ((directives.elevatedLevel as ElevatedLevel | undefined) ??
|
? (directives.elevatedLevel ??
|
||||||
(sessionEntry?.elevatedLevel as ElevatedLevel | undefined) ??
|
(sessionEntry?.elevatedLevel as ElevatedLevel | undefined) ??
|
||||||
(agentCfg?.elevatedDefault as ElevatedLevel | undefined) ??
|
(agentCfg?.elevatedDefault as ElevatedLevel | undefined) ??
|
||||||
"on")
|
"on")
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ export function resolveGroupRequireMention(params: {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function defaultGroupActivation(requireMention: boolean): "always" | "mention" {
|
export function defaultGroupActivation(requireMention: boolean): "always" | "mention" {
|
||||||
return requireMention === false ? "always" : "mention";
|
return !requireMention ? "always" : "mention";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildGroupIntro(params: {
|
export function buildGroupIntro(params: {
|
||||||
|
|
|
||||||
|
|
@ -436,7 +436,7 @@ export function resolveModelDirectiveSelection(params: {
|
||||||
});
|
});
|
||||||
return Object.assign({ candidate }, details);
|
return Object.assign({ candidate }, details);
|
||||||
})
|
})
|
||||||
.sort((a, b) => {
|
.toSorted((a, b) => {
|
||||||
if (b.score !== a.score) return b.score - a.score;
|
if (b.score !== a.score) return b.score - a.score;
|
||||||
if (a.isDefault !== b.isDefault) return a.isDefault ? -1 : 1;
|
if (a.isDefault !== b.isDefault) return a.isDefault ? -1 : 1;
|
||||||
if (a.variantMatchCount !== b.variantMatchCount)
|
if (a.variantMatchCount !== b.variantMatchCount)
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ export async function prependSystemEvents(params: {
|
||||||
const min = pick("minute");
|
const min = pick("minute");
|
||||||
const sec = pick("second");
|
const sec = pick("second");
|
||||||
const tz = [...parts]
|
const tz = [...parts]
|
||||||
.reverse()
|
.toReversed()
|
||||||
.find((part) => part.type === "timeZoneName")
|
.find((part) => part.type === "timeZoneName")
|
||||||
?.value?.trim();
|
?.value?.trim();
|
||||||
if (!yyyy || !mm || !dd || !hh || !min || !sec) return undefined;
|
if (!yyyy || !mm || !dd || !hh || !min || !sec) return undefined;
|
||||||
|
|
|
||||||
|
|
@ -235,10 +235,9 @@ export async function initSessionState(params: {
|
||||||
const baseEntry = !isNewSession && freshEntry ? entry : undefined;
|
const baseEntry = !isNewSession && freshEntry ? entry : undefined;
|
||||||
// Track the originating channel/to for announce routing (subagent announce-back).
|
// Track the originating channel/to for announce routing (subagent announce-back).
|
||||||
const lastChannelRaw = (ctx.OriginatingChannel as string | undefined) || baseEntry?.lastChannel;
|
const lastChannelRaw = (ctx.OriginatingChannel as string | undefined) || baseEntry?.lastChannel;
|
||||||
const lastToRaw = (ctx.OriginatingTo as string | undefined) || ctx.To || baseEntry?.lastTo;
|
const lastToRaw = ctx.OriginatingTo || ctx.To || baseEntry?.lastTo;
|
||||||
const lastAccountIdRaw = (ctx.AccountId as string | undefined) || baseEntry?.lastAccountId;
|
const lastAccountIdRaw = ctx.AccountId || baseEntry?.lastAccountId;
|
||||||
const lastThreadIdRaw =
|
const lastThreadIdRaw = ctx.MessageThreadId || baseEntry?.lastThreadId;
|
||||||
(ctx.MessageThreadId as string | number | undefined) || baseEntry?.lastThreadId;
|
|
||||||
const deliveryFields = normalizeSessionDeliveryFields({
|
const deliveryFields = normalizeSessionDeliveryFields({
|
||||||
deliveryContext: {
|
deliveryContext: {
|
||||||
channel: lastChannelRaw,
|
channel: lastChannelRaw,
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ export function formatRunStatus(entry: SubagentRunRecord) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sortSubagentRuns(runs: SubagentRunRecord[]) {
|
export function sortSubagentRuns(runs: SubagentRunRecord[]) {
|
||||||
return [...runs].sort((a, b) => {
|
return [...runs].toSorted((a, b) => {
|
||||||
const aTime = a.startedAt ?? a.createdAt ?? 0;
|
const aTime = a.startedAt ?? a.createdAt ?? 0;
|
||||||
const bTime = b.startedAt ?? b.createdAt ?? 0;
|
const bTime = b.startedAt ?? b.createdAt ?? 0;
|
||||||
return bTime - aTime;
|
return bTime - aTime;
|
||||||
|
|
|
||||||
|
|
@ -296,7 +296,7 @@ export async function snapshotDom(opts: {
|
||||||
awaitPromise: true,
|
awaitPromise: true,
|
||||||
returnByValue: true,
|
returnByValue: true,
|
||||||
});
|
});
|
||||||
const value = evaluated.result?.value as unknown;
|
const value = evaluated.result?.value;
|
||||||
if (!value || typeof value !== "object") return { nodes: [] };
|
if (!value || typeof value !== "object") return { nodes: [] };
|
||||||
const nodes = (value as { nodes?: unknown }).nodes;
|
const nodes = (value as { nodes?: unknown }).nodes;
|
||||||
return { nodes: Array.isArray(nodes) ? (nodes as DomSnapshotNode[]) : [] };
|
return { nodes: Array.isArray(nodes) ? (nodes as DomSnapshotNode[]) : [] };
|
||||||
|
|
|
||||||
|
|
@ -458,7 +458,7 @@ export async function ensureChromeExtensionRelayServer(opts: {
|
||||||
if ("error" in parsed && typeof parsed.error === "string" && parsed.error.trim()) {
|
if ("error" in parsed && typeof parsed.error === "string" && parsed.error.trim()) {
|
||||||
pending.reject(new Error(parsed.error));
|
pending.reject(new Error(parsed.error));
|
||||||
} else {
|
} else {
|
||||||
pending.resolve((parsed as ExtensionResponseMessage).result);
|
pending.resolve(parsed.result);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -183,18 +183,18 @@ describe("color allocation", () => {
|
||||||
|
|
||||||
it("allocates next unused color from palette", () => {
|
it("allocates next unused color from palette", () => {
|
||||||
// biome-ignore lint/style/noNonNullAssertion: Test file with known array
|
// biome-ignore lint/style/noNonNullAssertion: Test file with known array
|
||||||
const usedColors = new Set([PROFILE_COLORS[0]!.toUpperCase()]);
|
const usedColors = new Set([PROFILE_COLORS[0].toUpperCase()]);
|
||||||
expect(allocateColor(usedColors)).toBe(PROFILE_COLORS[1]);
|
expect(allocateColor(usedColors)).toBe(PROFILE_COLORS[1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("skips multiple used colors", () => {
|
it("skips multiple used colors", () => {
|
||||||
const usedColors = new Set([
|
const usedColors = new Set([
|
||||||
// biome-ignore lint/style/noNonNullAssertion: Test file with known array
|
// biome-ignore lint/style/noNonNullAssertion: Test file with known array
|
||||||
PROFILE_COLORS[0]!.toUpperCase(),
|
PROFILE_COLORS[0].toUpperCase(),
|
||||||
// biome-ignore lint/style/noNonNullAssertion: Test file with known array
|
// biome-ignore lint/style/noNonNullAssertion: Test file with known array
|
||||||
PROFILE_COLORS[1]!.toUpperCase(),
|
PROFILE_COLORS[1].toUpperCase(),
|
||||||
// biome-ignore lint/style/noNonNullAssertion: Test file with known array
|
// biome-ignore lint/style/noNonNullAssertion: Test file with known array
|
||||||
PROFILE_COLORS[2]!.toUpperCase(),
|
PROFILE_COLORS[2].toUpperCase(),
|
||||||
]);
|
]);
|
||||||
expect(allocateColor(usedColors)).toBe(PROFILE_COLORS[3]);
|
expect(allocateColor(usedColors)).toBe(PROFILE_COLORS[3]);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ export function allocateColor(usedColors: Set<string>): string {
|
||||||
// All colors used, cycle based on count
|
// All colors used, cycle based on count
|
||||||
const index = usedColors.size % PROFILE_COLORS.length;
|
const index = usedColors.size % PROFILE_COLORS.length;
|
||||||
// biome-ignore lint/style/noNonNullAssertion: Array is non-empty constant
|
// biome-ignore lint/style/noNonNullAssertion: Array is non-empty constant
|
||||||
return PROFILE_COLORS[index] ?? PROFILE_COLORS[0]!;
|
return PROFILE_COLORS[index] ?? PROFILE_COLORS[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUsedColors(
|
export function getUsedColors(
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ describe("pw-role-snapshot", () => {
|
||||||
expect(res.snapshot).toContain('- button "Save"');
|
expect(res.snapshot).toContain('- button "Save"');
|
||||||
expect(res.snapshot).not.toContain("navigation");
|
expect(res.snapshot).not.toContain("navigation");
|
||||||
expect(res.snapshot).not.toContain("heading");
|
expect(res.snapshot).not.toContain("heading");
|
||||||
expect(Object.keys(res.refs).sort()).toEqual(["e5", "e7"]);
|
expect(Object.keys(res.refs).toSorted()).toEqual(["e5", "e7"]);
|
||||||
expect(res.refs.e5).toMatchObject({ role: "link", name: "Home" });
|
expect(res.refs.e5).toMatchObject({ role: "link", name: "Home" });
|
||||||
expect(res.refs.e7).toMatchObject({ role: "button", name: "Save" });
|
expect(res.refs.e7).toMatchObject({ role: "button", name: "Save" });
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -55,13 +55,13 @@ describe("pw-tools-core", () => {
|
||||||
() =>
|
() =>
|
||||||
new Promise((r) => {
|
new Promise((r) => {
|
||||||
resolve1 = r;
|
resolve1 = r;
|
||||||
}) as Promise<unknown>,
|
}),
|
||||||
)
|
)
|
||||||
.mockImplementationOnce(
|
.mockImplementationOnce(
|
||||||
() =>
|
() =>
|
||||||
new Promise((r) => {
|
new Promise((r) => {
|
||||||
resolve2 = r;
|
resolve2 = r;
|
||||||
}) as Promise<unknown>,
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
currentPage = {
|
currentPage = {
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ export async function responseBodyViaPlaywright(opts: {
|
||||||
bodyText = new TextDecoder("utf-8").decode(buf);
|
bodyText = new TextDecoder("utf-8").decode(buf);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new Error(`Failed to read response body for "${url}": ${String(err)}`);
|
throw new Error(`Failed to read response body for "${url}": ${String(err)}`, { cause: err });
|
||||||
}
|
}
|
||||||
|
|
||||||
const trimmed = bodyText.length > maxChars ? bodyText.slice(0, maxChars) : bodyText;
|
const trimmed = bodyText.length > maxChars ? bodyText.slice(0, maxChars) : bodyText;
|
||||||
|
|
|
||||||
|
|
@ -200,6 +200,6 @@ export async function pdfViaPlaywright(opts: {
|
||||||
}): Promise<{ buffer: Buffer }> {
|
}): Promise<{ buffer: Buffer }> {
|
||||||
const page = await getPageForTargetId(opts);
|
const page = await getPageForTargetId(opts);
|
||||||
ensurePageState(page);
|
ensurePageState(page);
|
||||||
const buffer = await (page as Page).pdf({ printBackground: true });
|
const buffer = await page.pdf({ printBackground: true });
|
||||||
return { buffer };
|
return { buffer };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,8 @@ export async function setTimezoneViaPlaywright(opts: {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const msg = String(err);
|
const msg = String(err);
|
||||||
if (msg.includes("Timezone override is already in effect")) return;
|
if (msg.includes("Timezone override is already in effect")) return;
|
||||||
if (msg.includes("Invalid timezone")) throw new Error(`Invalid timezone ID: ${timezoneId}`);
|
if (msg.includes("Invalid timezone"))
|
||||||
|
throw new Error(`Invalid timezone ID: ${timezoneId}`, { cause: err });
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,7 @@ export function registerBrowserAgentActRoutes(
|
||||||
loadStateRaw === "load" ||
|
loadStateRaw === "load" ||
|
||||||
loadStateRaw === "domcontentloaded" ||
|
loadStateRaw === "domcontentloaded" ||
|
||||||
loadStateRaw === "networkidle"
|
loadStateRaw === "networkidle"
|
||||||
? (loadStateRaw as "load" | "domcontentloaded" | "networkidle")
|
? loadStateRaw
|
||||||
: undefined;
|
: undefined;
|
||||||
const fn = toStringOrEmpty(body.fn) || undefined;
|
const fn = toStringOrEmpty(body.fn) || undefined;
|
||||||
const timeoutMs = toNumber(body.timeoutMs) ?? undefined;
|
const timeoutMs = toNumber(body.timeoutMs) ?? undefined;
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export function registerBrowserAgentStorageRoutes(
|
||||||
secure: toBoolean(cookie.secure) ?? undefined,
|
secure: toBoolean(cookie.secure) ?? undefined,
|
||||||
sameSite:
|
sameSite:
|
||||||
cookie.sameSite === "Lax" || cookie.sameSite === "None" || cookie.sameSite === "Strict"
|
cookie.sameSite === "Lax" || cookie.sameSite === "None" || cookie.sameSite === "Strict"
|
||||||
? (cookie.sameSite as "Lax" | "None" | "Strict")
|
? cookie.sameSite
|
||||||
: undefined,
|
: undefined,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -270,7 +270,7 @@ export function registerBrowserAgentStorageRoutes(
|
||||||
const schemeRaw = toStringOrEmpty(body.colorScheme);
|
const schemeRaw = toStringOrEmpty(body.colorScheme);
|
||||||
const colorScheme =
|
const colorScheme =
|
||||||
schemeRaw === "dark" || schemeRaw === "light" || schemeRaw === "no-preference"
|
schemeRaw === "dark" || schemeRaw === "light" || schemeRaw === "no-preference"
|
||||||
? (schemeRaw as "dark" | "light" | "no-preference")
|
? schemeRaw
|
||||||
: schemeRaw === "none"
|
: schemeRaw === "none"
|
||||||
? null
|
? null
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export async function normalizeBrowserScreenshot(
|
||||||
const sideGrid = [sideStart, 1800, 1600, 1400, 1200, 1000, 800]
|
const sideGrid = [sideStart, 1800, 1600, 1400, 1200, 1000, 800]
|
||||||
.map((v) => Math.min(maxSide, v))
|
.map((v) => Math.min(maxSide, v))
|
||||||
.filter((v, i, arr) => v > 0 && arr.indexOf(v) === i)
|
.filter((v, i, arr) => v > 0 && arr.indexOf(v) === i)
|
||||||
.sort((a, b) => b - a);
|
.toSorted((a, b) => b - a);
|
||||||
|
|
||||||
let smallest: { buffer: Buffer; size: number } | null = null;
|
let smallest: { buffer: Buffer; size: number } | null = null;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -286,11 +286,11 @@ describe("browser control server", () => {
|
||||||
async () => {
|
async () => {
|
||||||
const base = await startServerAndBase();
|
const base = await startServerAndBase();
|
||||||
|
|
||||||
const select = (await postJson(`${base}/act`, {
|
const select = await postJson(`${base}/act`, {
|
||||||
kind: "select",
|
kind: "select",
|
||||||
ref: "5",
|
ref: "5",
|
||||||
values: ["a", "b"],
|
values: ["a", "b"],
|
||||||
})) as { ok: boolean };
|
});
|
||||||
expect(select.ok).toBe(true);
|
expect(select.ok).toBe(true);
|
||||||
expect(pwMocks.selectOptionViaPlaywright).toHaveBeenCalledWith({
|
expect(pwMocks.selectOptionViaPlaywright).toHaveBeenCalledWith({
|
||||||
cdpUrl: cdpBaseUrl,
|
cdpUrl: cdpBaseUrl,
|
||||||
|
|
@ -299,10 +299,10 @@ describe("browser control server", () => {
|
||||||
values: ["a", "b"],
|
values: ["a", "b"],
|
||||||
});
|
});
|
||||||
|
|
||||||
const fill = (await postJson(`${base}/act`, {
|
const fill = await postJson(`${base}/act`, {
|
||||||
kind: "fill",
|
kind: "fill",
|
||||||
fields: [{ ref: "6", type: "textbox", value: "hello" }],
|
fields: [{ ref: "6", type: "textbox", value: "hello" }],
|
||||||
})) as { ok: boolean };
|
});
|
||||||
expect(fill.ok).toBe(true);
|
expect(fill.ok).toBe(true);
|
||||||
expect(pwMocks.fillFormViaPlaywright).toHaveBeenCalledWith({
|
expect(pwMocks.fillFormViaPlaywright).toHaveBeenCalledWith({
|
||||||
cdpUrl: cdpBaseUrl,
|
cdpUrl: cdpBaseUrl,
|
||||||
|
|
@ -310,11 +310,11 @@ describe("browser control server", () => {
|
||||||
fields: [{ ref: "6", type: "textbox", value: "hello" }],
|
fields: [{ ref: "6", type: "textbox", value: "hello" }],
|
||||||
});
|
});
|
||||||
|
|
||||||
const resize = (await postJson(`${base}/act`, {
|
const resize = await postJson(`${base}/act`, {
|
||||||
kind: "resize",
|
kind: "resize",
|
||||||
width: 800,
|
width: 800,
|
||||||
height: 600,
|
height: 600,
|
||||||
})) as { ok: boolean };
|
});
|
||||||
expect(resize.ok).toBe(true);
|
expect(resize.ok).toBe(true);
|
||||||
expect(pwMocks.resizeViewportViaPlaywright).toHaveBeenCalledWith({
|
expect(pwMocks.resizeViewportViaPlaywright).toHaveBeenCalledWith({
|
||||||
cdpUrl: cdpBaseUrl,
|
cdpUrl: cdpBaseUrl,
|
||||||
|
|
@ -323,10 +323,10 @@ describe("browser control server", () => {
|
||||||
height: 600,
|
height: 600,
|
||||||
});
|
});
|
||||||
|
|
||||||
const wait = (await postJson(`${base}/act`, {
|
const wait = await postJson(`${base}/act`, {
|
||||||
kind: "wait",
|
kind: "wait",
|
||||||
timeMs: 5,
|
timeMs: 5,
|
||||||
})) as { ok: boolean };
|
});
|
||||||
expect(wait.ok).toBe(true);
|
expect(wait.ok).toBe(true);
|
||||||
expect(pwMocks.waitForViaPlaywright).toHaveBeenCalledWith({
|
expect(pwMocks.waitForViaPlaywright).toHaveBeenCalledWith({
|
||||||
cdpUrl: cdpBaseUrl,
|
cdpUrl: cdpBaseUrl,
|
||||||
|
|
@ -336,10 +336,10 @@ describe("browser control server", () => {
|
||||||
textGone: undefined,
|
textGone: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const evalRes = (await postJson(`${base}/act`, {
|
const evalRes = await postJson(`${base}/act`, {
|
||||||
kind: "evaluate",
|
kind: "evaluate",
|
||||||
fn: "() => 1",
|
fn: "() => 1",
|
||||||
})) as { ok: boolean; result?: unknown };
|
});
|
||||||
expect(evalRes.ok).toBe(true);
|
expect(evalRes.ok).toBe(true);
|
||||||
expect(evalRes.result).toBe("ok");
|
expect(evalRes.result).toBe("ok");
|
||||||
expect(pwMocks.evaluateViaPlaywright).toHaveBeenCalledWith({
|
expect(pwMocks.evaluateViaPlaywright).toHaveBeenCalledWith({
|
||||||
|
|
@ -358,17 +358,17 @@ describe("browser control server", () => {
|
||||||
cfgEvaluateEnabled = false;
|
cfgEvaluateEnabled = false;
|
||||||
const base = await startServerAndBase();
|
const base = await startServerAndBase();
|
||||||
|
|
||||||
const waitRes = (await postJson(`${base}/act`, {
|
const waitRes = await postJson(`${base}/act`, {
|
||||||
kind: "wait",
|
kind: "wait",
|
||||||
fn: "() => window.ready === true",
|
fn: "() => window.ready === true",
|
||||||
})) as { error?: string };
|
});
|
||||||
expect(waitRes.error).toContain("browser.evaluateEnabled=false");
|
expect(waitRes.error).toContain("browser.evaluateEnabled=false");
|
||||||
expect(pwMocks.waitForViaPlaywright).not.toHaveBeenCalled();
|
expect(pwMocks.waitForViaPlaywright).not.toHaveBeenCalled();
|
||||||
|
|
||||||
const res = (await postJson(`${base}/act`, {
|
const res = await postJson(`${base}/act`, {
|
||||||
kind: "evaluate",
|
kind: "evaluate",
|
||||||
fn: "() => 1",
|
fn: "() => 1",
|
||||||
})) as { error?: string };
|
});
|
||||||
|
|
||||||
expect(res.error).toContain("browser.evaluateEnabled=false");
|
expect(res.error).toContain("browser.evaluateEnabled=false");
|
||||||
expect(pwMocks.evaluateViaPlaywright).not.toHaveBeenCalled();
|
expect(pwMocks.evaluateViaPlaywright).not.toHaveBeenCalled();
|
||||||
|
|
@ -441,17 +441,14 @@ describe("browser control server", () => {
|
||||||
expect(consoleRes.ok).toBe(true);
|
expect(consoleRes.ok).toBe(true);
|
||||||
expect(Array.isArray(consoleRes.messages)).toBe(true);
|
expect(Array.isArray(consoleRes.messages)).toBe(true);
|
||||||
|
|
||||||
const pdf = (await postJson(`${base}/pdf`, {})) as {
|
const pdf = await postJson(`${base}/pdf`, {});
|
||||||
ok: boolean;
|
|
||||||
path?: string;
|
|
||||||
};
|
|
||||||
expect(pdf.ok).toBe(true);
|
expect(pdf.ok).toBe(true);
|
||||||
expect(typeof pdf.path).toBe("string");
|
expect(typeof pdf.path).toBe("string");
|
||||||
|
|
||||||
const shot = (await postJson(`${base}/screenshot`, {
|
const shot = await postJson(`${base}/screenshot`, {
|
||||||
element: "body",
|
element: "body",
|
||||||
type: "jpeg",
|
type: "jpeg",
|
||||||
})) as { ok: boolean; path?: string };
|
});
|
||||||
expect(shot.ok).toBe(true);
|
expect(shot.ok).toBe(true);
|
||||||
expect(typeof shot.path).toBe("string");
|
expect(typeof shot.path).toBe("string");
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -306,9 +306,9 @@ describe("browser control server", () => {
|
||||||
it("agent contract: navigation + common act commands", async () => {
|
it("agent contract: navigation + common act commands", async () => {
|
||||||
const base = await startServerAndBase();
|
const base = await startServerAndBase();
|
||||||
|
|
||||||
const nav = (await postJson(`${base}/navigate`, {
|
const nav = await postJson(`${base}/navigate`, {
|
||||||
url: "https://example.com",
|
url: "https://example.com",
|
||||||
})) as { ok: boolean; targetId?: string };
|
});
|
||||||
expect(nav.ok).toBe(true);
|
expect(nav.ok).toBe(true);
|
||||||
expect(typeof nav.targetId).toBe("string");
|
expect(typeof nav.targetId).toBe("string");
|
||||||
expect(pwMocks.navigateViaPlaywright).toHaveBeenCalledWith({
|
expect(pwMocks.navigateViaPlaywright).toHaveBeenCalledWith({
|
||||||
|
|
@ -317,12 +317,12 @@ describe("browser control server", () => {
|
||||||
url: "https://example.com",
|
url: "https://example.com",
|
||||||
});
|
});
|
||||||
|
|
||||||
const click = (await postJson(`${base}/act`, {
|
const click = await postJson(`${base}/act`, {
|
||||||
kind: "click",
|
kind: "click",
|
||||||
ref: "1",
|
ref: "1",
|
||||||
button: "left",
|
button: "left",
|
||||||
modifiers: ["Shift"],
|
modifiers: ["Shift"],
|
||||||
})) as { ok: boolean };
|
});
|
||||||
expect(click.ok).toBe(true);
|
expect(click.ok).toBe(true);
|
||||||
expect(pwMocks.clickViaPlaywright).toHaveBeenNthCalledWith(1, {
|
expect(pwMocks.clickViaPlaywright).toHaveBeenNthCalledWith(1, {
|
||||||
cdpUrl: cdpBaseUrl,
|
cdpUrl: cdpBaseUrl,
|
||||||
|
|
@ -343,11 +343,11 @@ describe("browser control server", () => {
|
||||||
/'selector' is not supported/i,
|
/'selector' is not supported/i,
|
||||||
);
|
);
|
||||||
|
|
||||||
const type = (await postJson(`${base}/act`, {
|
const type = await postJson(`${base}/act`, {
|
||||||
kind: "type",
|
kind: "type",
|
||||||
ref: "1",
|
ref: "1",
|
||||||
text: "",
|
text: "",
|
||||||
})) as { ok: boolean };
|
});
|
||||||
expect(type.ok).toBe(true);
|
expect(type.ok).toBe(true);
|
||||||
expect(pwMocks.typeViaPlaywright).toHaveBeenNthCalledWith(1, {
|
expect(pwMocks.typeViaPlaywright).toHaveBeenNthCalledWith(1, {
|
||||||
cdpUrl: cdpBaseUrl,
|
cdpUrl: cdpBaseUrl,
|
||||||
|
|
@ -358,10 +358,10 @@ describe("browser control server", () => {
|
||||||
slowly: false,
|
slowly: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const press = (await postJson(`${base}/act`, {
|
const press = await postJson(`${base}/act`, {
|
||||||
kind: "press",
|
kind: "press",
|
||||||
key: "Enter",
|
key: "Enter",
|
||||||
})) as { ok: boolean };
|
});
|
||||||
expect(press.ok).toBe(true);
|
expect(press.ok).toBe(true);
|
||||||
expect(pwMocks.pressKeyViaPlaywright).toHaveBeenCalledWith({
|
expect(pwMocks.pressKeyViaPlaywright).toHaveBeenCalledWith({
|
||||||
cdpUrl: cdpBaseUrl,
|
cdpUrl: cdpBaseUrl,
|
||||||
|
|
@ -369,10 +369,10 @@ describe("browser control server", () => {
|
||||||
key: "Enter",
|
key: "Enter",
|
||||||
});
|
});
|
||||||
|
|
||||||
const hover = (await postJson(`${base}/act`, {
|
const hover = await postJson(`${base}/act`, {
|
||||||
kind: "hover",
|
kind: "hover",
|
||||||
ref: "2",
|
ref: "2",
|
||||||
})) as { ok: boolean };
|
});
|
||||||
expect(hover.ok).toBe(true);
|
expect(hover.ok).toBe(true);
|
||||||
expect(pwMocks.hoverViaPlaywright).toHaveBeenCalledWith({
|
expect(pwMocks.hoverViaPlaywright).toHaveBeenCalledWith({
|
||||||
cdpUrl: cdpBaseUrl,
|
cdpUrl: cdpBaseUrl,
|
||||||
|
|
@ -380,10 +380,10 @@ describe("browser control server", () => {
|
||||||
ref: "2",
|
ref: "2",
|
||||||
});
|
});
|
||||||
|
|
||||||
const scroll = (await postJson(`${base}/act`, {
|
const scroll = await postJson(`${base}/act`, {
|
||||||
kind: "scrollIntoView",
|
kind: "scrollIntoView",
|
||||||
ref: "2",
|
ref: "2",
|
||||||
})) as { ok: boolean };
|
});
|
||||||
expect(scroll.ok).toBe(true);
|
expect(scroll.ok).toBe(true);
|
||||||
expect(pwMocks.scrollIntoViewViaPlaywright).toHaveBeenCalledWith({
|
expect(pwMocks.scrollIntoViewViaPlaywright).toHaveBeenCalledWith({
|
||||||
cdpUrl: cdpBaseUrl,
|
cdpUrl: cdpBaseUrl,
|
||||||
|
|
@ -391,11 +391,11 @@ describe("browser control server", () => {
|
||||||
ref: "2",
|
ref: "2",
|
||||||
});
|
});
|
||||||
|
|
||||||
const drag = (await postJson(`${base}/act`, {
|
const drag = await postJson(`${base}/act`, {
|
||||||
kind: "drag",
|
kind: "drag",
|
||||||
startRef: "3",
|
startRef: "3",
|
||||||
endRef: "4",
|
endRef: "4",
|
||||||
})) as { ok: boolean };
|
});
|
||||||
expect(drag.ok).toBe(true);
|
expect(drag.ok).toBe(true);
|
||||||
expect(pwMocks.dragViaPlaywright).toHaveBeenCalledWith({
|
expect(pwMocks.dragViaPlaywright).toHaveBeenCalledWith({
|
||||||
cdpUrl: cdpBaseUrl,
|
cdpUrl: cdpBaseUrl,
|
||||||
|
|
|
||||||
|
|
@ -347,7 +347,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
|
||||||
const deleteMessageDays = readNumberParam(actionParams, "deleteDays", {
|
const deleteMessageDays = readNumberParam(actionParams, "deleteDays", {
|
||||||
integer: true,
|
integer: true,
|
||||||
});
|
});
|
||||||
const discordAction = action as "timeout" | "kick" | "ban";
|
const discordAction = action;
|
||||||
return await handleDiscordAction(
|
return await handleDiscordAction(
|
||||||
{
|
{
|
||||||
action: discordAction,
|
action: discordAction,
|
||||||
|
|
|
||||||
|
|
@ -268,7 +268,7 @@ export function listChannelPluginCatalogEntries(
|
||||||
|
|
||||||
return Array.from(resolved.values())
|
return Array.from(resolved.values())
|
||||||
.map(({ entry }) => entry)
|
.map(({ entry }) => entry)
|
||||||
.sort((a, b) => {
|
.toSorted((a, b) => {
|
||||||
const orderA = a.meta.order ?? 999;
|
const orderA = a.meta.order ?? 999;
|
||||||
const orderB = b.meta.order ?? 999;
|
const orderB = b.meta.order ?? 999;
|
||||||
if (orderA !== orderB) return orderA - orderB;
|
if (orderA !== orderB) return orderA - orderB;
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ export function setAccountEnabledInConfigSection(params: {
|
||||||
} as OpenClawConfig;
|
} as OpenClawConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseAccounts = (base?.accounts ?? {}) as Record<string, Record<string, unknown>>;
|
const baseAccounts = base?.accounts ?? {};
|
||||||
const existing = baseAccounts[accountKey] ?? {};
|
const existing = baseAccounts[accountKey] ?? {};
|
||||||
return {
|
return {
|
||||||
...params.cfg,
|
...params.cfg,
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ describe("directory (config-backed)", () => {
|
||||||
query: null,
|
query: null,
|
||||||
limit: null,
|
limit: null,
|
||||||
});
|
});
|
||||||
expect(peers?.map((e) => e.id).sort()).toEqual([
|
expect(peers?.map((e) => e.id).toSorted()).toEqual([
|
||||||
"user:u123",
|
"user:u123",
|
||||||
"user:u234",
|
"user:u234",
|
||||||
"user:u777",
|
"user:u777",
|
||||||
|
|
@ -73,7 +73,7 @@ describe("directory (config-backed)", () => {
|
||||||
query: null,
|
query: null,
|
||||||
limit: null,
|
limit: null,
|
||||||
});
|
});
|
||||||
expect(peers?.map((e) => e.id).sort()).toEqual(["user:111", "user:12345", "user:222"]);
|
expect(peers?.map((e) => e.id).toSorted()).toEqual(["user:111", "user:12345", "user:222"]);
|
||||||
|
|
||||||
const groups = await listDiscordDirectoryGroupsFromConfig({
|
const groups = await listDiscordDirectoryGroupsFromConfig({
|
||||||
cfg,
|
cfg,
|
||||||
|
|
@ -81,7 +81,7 @@ describe("directory (config-backed)", () => {
|
||||||
query: null,
|
query: null,
|
||||||
limit: null,
|
limit: null,
|
||||||
});
|
});
|
||||||
expect(groups?.map((e) => e.id).sort()).toEqual(["channel:555", "channel:666"]);
|
expect(groups?.map((e) => e.id).toSorted()).toEqual(["channel:555", "channel:666"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("lists Telegram peers/groups from config", async () => {
|
it("lists Telegram peers/groups from config", async () => {
|
||||||
|
|
@ -102,7 +102,7 @@ describe("directory (config-backed)", () => {
|
||||||
query: null,
|
query: null,
|
||||||
limit: null,
|
limit: null,
|
||||||
});
|
});
|
||||||
expect(peers?.map((e) => e.id).sort()).toEqual(["123", "456", "@alice", "@bob"]);
|
expect(peers?.map((e) => e.id).toSorted()).toEqual(["123", "456", "@alice", "@bob"]);
|
||||||
|
|
||||||
const groups = await listTelegramDirectoryGroupsFromConfig({
|
const groups = await listTelegramDirectoryGroupsFromConfig({
|
||||||
cfg,
|
cfg,
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ function dedupeChannels(channels: ChannelPlugin[]): ChannelPlugin[] {
|
||||||
|
|
||||||
export function listChannelPlugins(): ChannelPlugin[] {
|
export function listChannelPlugins(): ChannelPlugin[] {
|
||||||
const combined = dedupeChannels(listPluginChannels());
|
const combined = dedupeChannels(listPluginChannels());
|
||||||
return combined.sort((a, b) => {
|
return combined.toSorted((a, b) => {
|
||||||
const indexA = CHAT_CHANNEL_ORDER.indexOf(a.id as ChatChannelId);
|
const indexA = CHAT_CHANNEL_ORDER.indexOf(a.id as ChatChannelId);
|
||||||
const indexB = CHAT_CHANNEL_ORDER.indexOf(b.id as ChatChannelId);
|
const indexB = CHAT_CHANNEL_ORDER.indexOf(b.id as ChatChannelId);
|
||||||
const orderA = a.meta.order ?? (indexA === -1 ? 999 : indexA);
|
const orderA = a.meta.order ?? (indexA === -1 ? 999 : indexA);
|
||||||
|
|
|
||||||
|
|
@ -33,11 +33,11 @@ export async function promptChannelAccessPolicy(params: {
|
||||||
options.push({ value: "disabled", label: "Disabled (block all channels)" });
|
options.push({ value: "disabled", label: "Disabled (block all channels)" });
|
||||||
}
|
}
|
||||||
const initialValue = params.currentPolicy ?? "allowlist";
|
const initialValue = params.currentPolicy ?? "allowlist";
|
||||||
return (await params.prompter.select({
|
return await params.prompter.select({
|
||||||
message: `${params.label} access`,
|
message: `${params.label} access`,
|
||||||
options,
|
options,
|
||||||
initialValue,
|
initialValue,
|
||||||
})) as ChannelAccessPolicy;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function promptChannelAllowlist(params: {
|
export async function promptChannelAllowlist(params: {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import type { PromptAccountId, PromptAccountIdParams } from "../onboarding-types
|
||||||
export const promptAccountId: PromptAccountId = async (params: PromptAccountIdParams) => {
|
export const promptAccountId: PromptAccountId = async (params: PromptAccountIdParams) => {
|
||||||
const existingIds = params.listAccountIds(params.cfg);
|
const existingIds = params.listAccountIds(params.cfg);
|
||||||
const initial = params.currentId?.trim() || params.defaultAccountId || DEFAULT_ACCOUNT_ID;
|
const initial = params.currentId?.trim() || params.defaultAccountId || DEFAULT_ACCOUNT_ID;
|
||||||
const choice = (await params.prompter.select({
|
const choice = await params.prompter.select({
|
||||||
message: `${params.label} account`,
|
message: `${params.label} account`,
|
||||||
options: [
|
options: [
|
||||||
...existingIds.map((id) => ({
|
...existingIds.map((id) => ({
|
||||||
|
|
@ -14,7 +14,7 @@ export const promptAccountId: PromptAccountId = async (params: PromptAccountIdPa
|
||||||
{ value: "__new__", label: "Add a new account" },
|
{ value: "__new__", label: "Add a new account" },
|
||||||
],
|
],
|
||||||
initialValue: initial,
|
initialValue: initial,
|
||||||
})) as string;
|
});
|
||||||
|
|
||||||
if (choice !== "__new__") return normalizeAccountId(choice);
|
if (choice !== "__new__") return normalizeAccountId(choice);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,13 +107,13 @@ async function promptWhatsAppAllowFrom(
|
||||||
"WhatsApp DM access",
|
"WhatsApp DM access",
|
||||||
);
|
);
|
||||||
|
|
||||||
const phoneMode = (await prompter.select({
|
const phoneMode = await prompter.select({
|
||||||
message: "WhatsApp phone setup",
|
message: "WhatsApp phone setup",
|
||||||
options: [
|
options: [
|
||||||
{ value: "personal", label: "This is my personal phone number" },
|
{ value: "personal", label: "This is my personal phone number" },
|
||||||
{ value: "separate", label: "Separate phone just for OpenClaw" },
|
{ value: "separate", label: "Separate phone just for OpenClaw" },
|
||||||
],
|
],
|
||||||
})) as "personal" | "separate";
|
});
|
||||||
|
|
||||||
if (phoneMode === "personal") {
|
if (phoneMode === "personal") {
|
||||||
await prompter.note(
|
await prompter.note(
|
||||||
|
|
@ -187,13 +187,13 @@ async function promptWhatsAppAllowFrom(
|
||||||
{ value: "list", label: "Set allowFrom to specific numbers" },
|
{ value: "list", label: "Set allowFrom to specific numbers" },
|
||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
const mode = (await prompter.select({
|
const mode = await prompter.select({
|
||||||
message: "WhatsApp allowFrom (optional pre-allowlist)",
|
message: "WhatsApp allowFrom (optional pre-allowlist)",
|
||||||
options: allowOptions.map((opt) => ({
|
options: allowOptions.map((opt) => ({
|
||||||
value: opt.value,
|
value: opt.value,
|
||||||
label: opt.label,
|
label: opt.label,
|
||||||
})),
|
})),
|
||||||
})) as (typeof allowOptions)[number]["value"];
|
});
|
||||||
|
|
||||||
if (mode === "keep") {
|
if (mode === "keep") {
|
||||||
// Keep allowFrom as-is.
|
// Keep allowFrom as-is.
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ function getSessionRecipients(cfg: OpenClawConfig) {
|
||||||
updatedAt: entry?.updatedAt ?? 0,
|
updatedAt: entry?.updatedAt ?? 0,
|
||||||
}))
|
}))
|
||||||
.filter(({ to }) => to.length > 1)
|
.filter(({ to }) => to.length > 1)
|
||||||
.sort((a, b) => b.updatedAt - a.updatedAt);
|
.toSorted((a, b) => b.updatedAt - a.updatedAt);
|
||||||
|
|
||||||
// Dedupe while preserving recency ordering.
|
// Dedupe while preserving recency ordering.
|
||||||
const seen = new Set<string>();
|
const seen = new Set<string>();
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue