@agent-native/core 0.157.8 → 0.157.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/actions/purge-first-party-analytics-postgres.ts +26 -8
- package/corpus/templates/analytics/scripts/first-party-analytics-postgres-retention-dry-run.sql +261 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +3 -1
- package/corpus/templates/analytics/server/lib/first-party-analytics-purge.ts +75 -31
- package/corpus/templates/analytics/server/lib/workspace-feature-flags.ts +4 -1
- package/corpus/templates/analytics/server/plugins/db.ts +139 -0
- package/corpus/templates/slides/AGENTS.md +14 -10
- package/corpus/templates/slides/actions/export-pptx.ts +1311 -121
- package/corpus/templates/slides/actions/import-file.ts +28 -1
- package/corpus/templates/slides/actions/import-pptx.ts +69 -42
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +222 -29
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +71 -0
- package/corpus/templates/slides/app/global.css +19 -1
- package/corpus/templates/slides/app/lib/export-google-slides-client.ts +44 -5
- package/corpus/templates/slides/app/lib/export-pdf-client.ts +13 -6
- package/corpus/templates/slides/app/lib/export-pptx-client.ts +601 -30
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +18 -1
- package/corpus/templates/slides/server/handlers/import/html-converter.ts +1240 -22
- package/corpus/templates/slides/server/handlers/import/pdf-fidelity-parser.ts +329 -64
- package/corpus/templates/slides/server/handlers/import/pptx-assets.ts +24 -0
- package/corpus/templates/slides/server/lib/source-import.ts +10 -1
- package/corpus/templates/slides/shared/aspect-ratios.ts +13 -1
- package/dist/agent/harness/migrations.d.ts +4 -0
- package/dist/agent/harness/migrations.js +60 -0
- package/dist/agent/production-agent.d.ts +2 -0
- package/dist/agent/production-agent.js +15 -0
- package/dist/agent/run-loop-with-resume.d.ts +1 -0
- package/dist/agent/run-loop-with-resume.js +44 -12
- package/dist/agent/run-migrations.d.ts +8 -0
- package/dist/agent/run-migrations.js +61 -0
- package/dist/automations/service.d.ts +6 -0
- package/dist/automations/service.js +35 -0
- package/dist/chat-threads/migrations.d.ts +1 -0
- package/dist/chat-threads/migrations.js +1 -0
- package/dist/chat-threads/schema-migrations.d.ts +8 -0
- package/dist/chat-threads/schema-migrations.js +60 -0
- package/dist/cli/code-agent-connector.d.ts +4 -1
- package/dist/cli/code-agent-connector.js +154 -23
- package/dist/cli/code-agent-executor.js +255 -13
- package/dist/cli/code-agent-runs.d.ts +1 -0
- package/dist/cli/code-agent-runs.js +2 -1
- package/dist/cli/portal-workspace.d.ts +49 -0
- package/dist/cli/portal-workspace.js +320 -0
- package/dist/client/AssistantChat.d.ts +1 -1
- package/dist/client/AssistantChat.js +43 -61
- package/dist/client/ConnectBuilderCard.js +33 -5
- package/dist/client/agent-chat-adapter.js +4 -0
- package/dist/client/analytics-platform.d.ts +2 -0
- package/dist/client/analytics-platform.js +12 -0
- package/dist/client/analytics.d.ts +3 -0
- package/dist/client/analytics.js +10 -1
- package/dist/client/chat/index.d.ts +1 -0
- package/dist/client/chat/index.js +1 -0
- package/dist/client/chat/message-components.d.ts +2 -0
- package/dist/client/chat/message-components.js +11 -1
- package/dist/client/chat/run-recovery.d.ts +2 -1
- package/dist/client/chat/run-recovery.js +2 -2
- package/dist/client/chat-first/apps-rail.d.ts +1 -1
- package/dist/client/chat-first/apps-rail.js +11 -11
- package/dist/client/chat-first/surface-tabs.d.ts +1 -1
- package/dist/client/chat-first/surface-tabs.js +38 -36
- package/dist/client/chat-first/types.d.ts +5 -0
- package/dist/client/chat-first.d.ts +1 -1
- package/dist/client/chat-first.js +4 -4
- package/dist/client/desktop-local-code-change.d.ts +10 -0
- package/dist/client/desktop-local-code-change.js +16 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +1 -0
- package/dist/client/observability/ThumbsFeedback.js +53 -26
- package/dist/client/observability/useObservability.d.ts +1 -1
- package/dist/client/observability/useObservability.js +6 -3
- package/dist/client/terminal/AgentTerminal.d.ts +9 -1
- package/dist/client/terminal/AgentTerminal.js +38 -3
- package/dist/client/terminal/index.d.ts +1 -1
- package/dist/client/terminal/index.js +1 -1
- package/dist/client/track.js +9 -1
- package/dist/client/use-action.js +3 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/deploy/build.js +1 -1
- package/dist/feature-flags/a2a-action-route.js +4 -2
- package/dist/feature-flags/permissions.d.ts +2 -0
- package/dist/feature-flags/permissions.js +6 -1
- package/dist/ingestion/pptx.d.ts +92 -1
- package/dist/ingestion/pptx.js +1180 -115
- package/dist/integrations/index.d.ts +3 -3
- package/dist/integrations/index.js +2 -2
- package/dist/integrations/plugin.js +95 -6
- package/dist/integrations/remote-commands-store.d.ts +5 -0
- package/dist/integrations/remote-commands-store.js +63 -22
- package/dist/integrations/remote-devices-store.d.ts +2 -1
- package/dist/integrations/remote-devices-store.js +46 -0
- package/dist/integrations/remote-types.d.ts +20 -0
- package/dist/jobs/actions/list-recurring-jobs.d.ts +3 -0
- package/dist/jobs/actions/list-recurring-jobs.js +5 -1
- package/dist/jobs/frontmatter.d.ts +16 -0
- package/dist/jobs/frontmatter.js +53 -0
- package/dist/jobs/remote-execution.d.ts +42 -0
- package/dist/jobs/remote-execution.js +287 -0
- package/dist/jobs/scheduler.js +102 -3
- package/dist/jobs/tools.js +50 -3
- package/dist/localization/core-messages/ar-SA.js +4 -0
- package/dist/localization/core-messages/de-DE.js +4 -0
- package/dist/localization/core-messages/en-US.d.ts +4 -0
- package/dist/localization/core-messages/en-US.js +4 -0
- package/dist/localization/core-messages/es-ES.js +4 -0
- package/dist/localization/core-messages/fr-FR.js +4 -0
- package/dist/localization/core-messages/hi-IN.js +4 -0
- package/dist/localization/core-messages/ja-JP.js +4 -0
- package/dist/localization/core-messages/ko-KR.js +4 -0
- package/dist/localization/core-messages/pt-BR.js +4 -0
- package/dist/localization/core-messages/zh-CN.js +4 -0
- package/dist/localization/core-messages/zh-TW.js +4 -0
- package/dist/localization/core-messages.d.ts +4 -0
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/feedback.js +40 -16
- package/dist/observability/routes.d.ts +6 -6
- package/dist/observability/routes.js +15 -3
- package/dist/observability/store.js +2 -0
- package/dist/observability/traces.js +5 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +16 -16
- package/dist/provider-api/actions/provider-api.d.ts +13 -13
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-routes.d.ts +7 -5
- package/dist/server/action-routes.js +13 -6
- package/dist/server/agent-chat-plugin.js +6 -0
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-run-context.d.ts +1 -0
- package/dist/server/agent-run-context.js +9 -0
- package/dist/server/better-auth-instance.js +4 -0
- package/dist/server/core-routes-plugin.js +8 -1
- package/dist/server/realtime-voice.js +2 -1
- package/dist/server/release-migrations.js +16 -0
- package/dist/server/request-context.d.ts +2 -0
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/analytics-platform.d.ts +6 -0
- package/dist/shared/analytics-platform.js +14 -0
- package/dist/shared/mcp-embed-headers.d.ts +1 -1
- package/dist/shared/mcp-embed-headers.js +1 -1
- package/dist/styles/agent-native.css +83 -103
- package/dist/terminal/cli-registry.js +5 -0
- package/dist/tracking/registry.js +9 -1
- package/dist/triggers/actions/list-automations.d.ts +3 -0
- package/dist/triggers/actions/list-automations.js +3 -0
- package/dist/triggers/actions/manage-automation.d.ts +9 -0
- package/dist/triggers/actions/manage-automation.js +14 -2
- package/dist/triggers/actions.js +82 -7
- package/dist/usage/migrations.d.ts +4 -0
- package/dist/usage/migrations.js +45 -0
- package/docs/content/template-content-developers.mdx +48 -0
- package/docs/content/toolkit-ui.mdx +57 -0
- package/package.json +3 -3
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { enqueueRemoteCommand, getRemoteCommand, getRemoteCommandByIdempotencyKey, } from "../integrations/remote-commands-store.js";
|
|
2
|
+
import { getRemoteDeviceForOwner, getRemoteExecutionCapabilities, } from "../integrations/remote-devices-store.js";
|
|
3
|
+
import { organizationResourceOwner, resourceGetByPath, resourcePutIfCurrent, } from "../resources/store.js";
|
|
4
|
+
import { buildJobResourceContent, parseJobResource, } from "./frontmatter.js";
|
|
5
|
+
import { finishAutomationRun, startAutomationRun } from "./run-history.js";
|
|
6
|
+
/** A remote run is allowed to be long-lived, but never indefinitely invisible. */
|
|
7
|
+
export const REMOTE_AUTOMATION_MAX_ACTIVE_MS = 24 * 60 * 60_000;
|
|
8
|
+
function isRecord(value) {
|
|
9
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
10
|
+
}
|
|
11
|
+
function readString(...values) {
|
|
12
|
+
for (const value of values) {
|
|
13
|
+
if (typeof value === "string" && value.trim())
|
|
14
|
+
return value.trim();
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
function automationName(path) {
|
|
19
|
+
return path.replace(/^jobs\//, "").replace(/\.md$/, "");
|
|
20
|
+
}
|
|
21
|
+
function remoteRequestId(resource, meta, now) {
|
|
22
|
+
const runAt = meta.lastRun || now.toISOString();
|
|
23
|
+
return `remote-automation:${resource.owner}:${resource.path}:${runAt}`.slice(0, 512);
|
|
24
|
+
}
|
|
25
|
+
function assertRemoteHostCanRun(device, meta) {
|
|
26
|
+
if (device.status !== "active") {
|
|
27
|
+
throw new Error(`Execution host "${device.label}" is revoked or inactive.`);
|
|
28
|
+
}
|
|
29
|
+
const capabilities = getRemoteExecutionCapabilities(device);
|
|
30
|
+
if (!capabilities)
|
|
31
|
+
return;
|
|
32
|
+
if (capabilities.acceptsScheduledWork !== true) {
|
|
33
|
+
throw new Error(`Execution host "${device.label}" does not accept scheduled work.`);
|
|
34
|
+
}
|
|
35
|
+
if (capabilities.workloads?.length &&
|
|
36
|
+
!capabilities.workloads.includes("scheduled-code")) {
|
|
37
|
+
throw new Error(`Execution host "${device.label}" does not advertise scheduled-code work.`);
|
|
38
|
+
}
|
|
39
|
+
if (meta.executionEngine &&
|
|
40
|
+
capabilities.engines?.length &&
|
|
41
|
+
!capabilities.engines.includes(meta.executionEngine)) {
|
|
42
|
+
throw new Error(`Execution host "${device.label}" does not advertise engine "${meta.executionEngine}".`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function resolveRemoteHost(input) {
|
|
46
|
+
const device = await getRemoteDeviceForOwner({
|
|
47
|
+
id: input.hostId,
|
|
48
|
+
ownerEmail: input.ownerEmail,
|
|
49
|
+
orgId: input.orgId,
|
|
50
|
+
});
|
|
51
|
+
if (!device) {
|
|
52
|
+
throw new Error(`Execution host "${input.hostId}" is not paired with this account and organization.`);
|
|
53
|
+
}
|
|
54
|
+
return device;
|
|
55
|
+
}
|
|
56
|
+
function remoteRunIdFromCommand(command) {
|
|
57
|
+
const result = isRecord(command.result) ? command.result : undefined;
|
|
58
|
+
const nested = isRecord(result?.result) ? result.result : result;
|
|
59
|
+
const run = isRecord(nested?.run) ? nested.run : undefined;
|
|
60
|
+
return readString(run?.id, run?.runId);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Queue one host-targeted automation. The request key is derived from the
|
|
64
|
+
* persisted start timestamp, so a worker crash between the queue write and
|
|
65
|
+
* resource bookkeeping cannot create a second remote run.
|
|
66
|
+
*/
|
|
67
|
+
export async function dispatchRemoteAutomation(input) {
|
|
68
|
+
const now = input.now ?? new Date();
|
|
69
|
+
const hostId = input.meta.executionHostId?.trim();
|
|
70
|
+
if (!hostId)
|
|
71
|
+
throw new Error("A remote automation requires executionHostId.");
|
|
72
|
+
const device = await resolveRemoteHost({
|
|
73
|
+
hostId,
|
|
74
|
+
ownerEmail: input.ownerEmail,
|
|
75
|
+
orgId: input.orgId,
|
|
76
|
+
});
|
|
77
|
+
assertRemoteHostCanRun(device, input.meta);
|
|
78
|
+
const latest = await resourceGetByPath(input.resource.owner, input.resource.path);
|
|
79
|
+
if (!latest || latest.id !== input.resource.id) {
|
|
80
|
+
throw new Error("The automation changed before remote dispatch.");
|
|
81
|
+
}
|
|
82
|
+
const parsed = parseJobResource(latest.content);
|
|
83
|
+
if (parsed.meta.lastStatus !== "running") {
|
|
84
|
+
throw new Error("The automation is no longer marked as running.");
|
|
85
|
+
}
|
|
86
|
+
const requestId = parsed.meta.remoteRequestId ||
|
|
87
|
+
input.meta.remoteRequestId ||
|
|
88
|
+
remoteRequestId(latest, parsed.meta, now);
|
|
89
|
+
let currentResource = latest;
|
|
90
|
+
let currentMeta = parsed.meta;
|
|
91
|
+
if (!currentMeta.remoteRequestId) {
|
|
92
|
+
const withRequest = { ...currentMeta, remoteRequestId: requestId };
|
|
93
|
+
const claimed = await resourcePutIfCurrent({
|
|
94
|
+
owner: latest.owner,
|
|
95
|
+
path: latest.path,
|
|
96
|
+
content: buildJobResourceContent(withRequest, parsed.body),
|
|
97
|
+
expectedId: latest.id,
|
|
98
|
+
expectedUpdatedAt: latest.updatedAt,
|
|
99
|
+
expectedContent: latest.content,
|
|
100
|
+
});
|
|
101
|
+
if (!claimed)
|
|
102
|
+
throw new Error("The automation changed before remote dispatch.");
|
|
103
|
+
currentResource = claimed;
|
|
104
|
+
currentMeta = withRequest;
|
|
105
|
+
}
|
|
106
|
+
let automationRunId = input.historyId || currentMeta.remoteAutomationRunId;
|
|
107
|
+
let createdHistory = false;
|
|
108
|
+
if (!automationRunId) {
|
|
109
|
+
try {
|
|
110
|
+
const historyOwner = input.orgId
|
|
111
|
+
? organizationResourceOwner(input.orgId)
|
|
112
|
+
: input.resource.owner === "__shared__"
|
|
113
|
+
? input.ownerEmail
|
|
114
|
+
: input.resource.owner;
|
|
115
|
+
automationRunId = await startAutomationRun({
|
|
116
|
+
owner: historyOwner,
|
|
117
|
+
automation: automationName(input.resource.path),
|
|
118
|
+
path: input.resource.path,
|
|
119
|
+
scope: input.orgId ? "organization" : "personal",
|
|
120
|
+
orgId: input.orgId ?? null,
|
|
121
|
+
appId: input.appId,
|
|
122
|
+
});
|
|
123
|
+
createdHistory = true;
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
console.warn(`[automations] Could not open remote history for "${input.resource.path}"; queueing without it:`, error);
|
|
127
|
+
automationRunId = undefined;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
const command = await enqueueRemoteCommand({
|
|
132
|
+
deviceId: device.id,
|
|
133
|
+
ownerEmail: input.ownerEmail,
|
|
134
|
+
orgId: input.orgId ?? null,
|
|
135
|
+
kind: "create-run",
|
|
136
|
+
idempotencyKey: requestId,
|
|
137
|
+
platform: "automation",
|
|
138
|
+
externalThreadId: `automation:${automationName(input.resource.path)}`,
|
|
139
|
+
params: {
|
|
140
|
+
prompt: input.prompt,
|
|
141
|
+
title: input.title,
|
|
142
|
+
cwd: currentMeta.executionCwd,
|
|
143
|
+
goalId: `automation:${automationName(input.resource.path)}`,
|
|
144
|
+
permissionMode: "full-auto",
|
|
145
|
+
engine: currentMeta.executionEngine,
|
|
146
|
+
model: currentMeta.model,
|
|
147
|
+
workload: "scheduled-code",
|
|
148
|
+
metadata: {
|
|
149
|
+
source: "scheduled-automation",
|
|
150
|
+
automation: {
|
|
151
|
+
name: automationName(input.resource.path),
|
|
152
|
+
path: input.resource.path,
|
|
153
|
+
runAt: currentMeta.lastRun,
|
|
154
|
+
},
|
|
155
|
+
...(automationRunId ? { automationRunId } : {}),
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
const nextMeta = {
|
|
160
|
+
...currentMeta,
|
|
161
|
+
remoteRequestId: requestId,
|
|
162
|
+
remoteCommandId: command.id,
|
|
163
|
+
remoteRunId: remoteRunIdFromCommand(command),
|
|
164
|
+
remoteAutomationRunId: automationRunId,
|
|
165
|
+
remoteAdvanceSchedule: input.advanceSchedule !== false,
|
|
166
|
+
};
|
|
167
|
+
const persisted = await resourcePutIfCurrent({
|
|
168
|
+
owner: currentResource.owner,
|
|
169
|
+
path: currentResource.path,
|
|
170
|
+
content: buildJobResourceContent(nextMeta, parsed.body),
|
|
171
|
+
expectedId: currentResource.id,
|
|
172
|
+
expectedUpdatedAt: currentResource.updatedAt,
|
|
173
|
+
expectedContent: currentResource.content,
|
|
174
|
+
});
|
|
175
|
+
if (!persisted) {
|
|
176
|
+
console.warn(`[automations] Remote command ${command.id} was queued but the automation bookkeeping changed concurrently.`);
|
|
177
|
+
}
|
|
178
|
+
return { command, requestId, automationRunId };
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
if (createdHistory && automationRunId) {
|
|
182
|
+
await finishAutomationRun(automationRunId, "error", error instanceof Error ? error.message : "Remote dispatch failed.").catch(() => undefined);
|
|
183
|
+
}
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
export async function getRemoteAutomationStatus(input) {
|
|
188
|
+
if (!input.meta.executionHostId)
|
|
189
|
+
return { state: "not-remote" };
|
|
190
|
+
if (!input.meta.remoteRequestId) {
|
|
191
|
+
return {
|
|
192
|
+
state: "failed",
|
|
193
|
+
error: "Remote dispatch bookkeeping is missing its request id.",
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
const device = await resolveRemoteHost({
|
|
197
|
+
hostId: input.meta.executionHostId,
|
|
198
|
+
ownerEmail: input.ownerEmail,
|
|
199
|
+
orgId: input.orgId,
|
|
200
|
+
});
|
|
201
|
+
if (device.status !== "active") {
|
|
202
|
+
return {
|
|
203
|
+
state: "failed",
|
|
204
|
+
error: `Execution host "${device.label}" is revoked or inactive.`,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
const command = input.meta.remoteCommandId
|
|
208
|
+
? await getRemoteCommand(input.meta.remoteCommandId)
|
|
209
|
+
: await getRemoteCommandByIdempotencyKey({
|
|
210
|
+
deviceId: device.id,
|
|
211
|
+
idempotencyKey: input.meta.remoteRequestId,
|
|
212
|
+
});
|
|
213
|
+
if (command &&
|
|
214
|
+
(command.deviceId !== device.id ||
|
|
215
|
+
command.ownerEmail !== input.ownerEmail ||
|
|
216
|
+
command.orgId !== (input.orgId ?? null))) {
|
|
217
|
+
return {
|
|
218
|
+
state: "failed",
|
|
219
|
+
error: "Remote dispatch bookkeeping failed its host ownership check.",
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
if (!command) {
|
|
223
|
+
const startedAt = input.meta.lastRun
|
|
224
|
+
? Date.parse(input.meta.lastRun)
|
|
225
|
+
: Number.NaN;
|
|
226
|
+
const age = (input.now ?? new Date()).getTime() - startedAt;
|
|
227
|
+
return age >= REMOTE_AUTOMATION_MAX_ACTIVE_MS
|
|
228
|
+
? {
|
|
229
|
+
state: "failed",
|
|
230
|
+
error: "Remote dispatch did not produce a command within 24 hours.",
|
|
231
|
+
}
|
|
232
|
+
: { state: "active" };
|
|
233
|
+
}
|
|
234
|
+
if (command.status === "failed") {
|
|
235
|
+
return {
|
|
236
|
+
state: "failed",
|
|
237
|
+
command,
|
|
238
|
+
error: command.errorMessage ?? "The remote command failed.",
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
if (command.status !== "completed") {
|
|
242
|
+
const activeAt = command.updatedAt || command.claimedAt || command.createdAt;
|
|
243
|
+
const age = (input.now ?? new Date()).getTime() - activeAt;
|
|
244
|
+
if (Number.isFinite(age) && age >= REMOTE_AUTOMATION_MAX_ACTIVE_MS) {
|
|
245
|
+
return {
|
|
246
|
+
state: "failed",
|
|
247
|
+
command,
|
|
248
|
+
error: "The remote command remained active for more than 24 hours.",
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
return { state: "active", command };
|
|
252
|
+
}
|
|
253
|
+
const result = isRecord(command.result) ? command.result : undefined;
|
|
254
|
+
const nested = isRecord(result?.result) ? result.result : result;
|
|
255
|
+
if (nested?.ok === false) {
|
|
256
|
+
return {
|
|
257
|
+
state: "failed",
|
|
258
|
+
command,
|
|
259
|
+
error: command.errorMessage ??
|
|
260
|
+
readString(nested.error) ??
|
|
261
|
+
"Remote run failed.",
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
const run = isRecord(nested?.run) ? nested.run : undefined;
|
|
265
|
+
const runStatus = readString(run?.status);
|
|
266
|
+
if (runStatus === "completed")
|
|
267
|
+
return { state: "completed", command };
|
|
268
|
+
if (runStatus === "errored" || runStatus === "paused") {
|
|
269
|
+
return {
|
|
270
|
+
state: "failed",
|
|
271
|
+
command,
|
|
272
|
+
error: readString(run?.error, run?.errorMessage) ??
|
|
273
|
+
command.errorMessage ??
|
|
274
|
+
`Remote run ended with status ${runStatus}.`,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
if (runStatus === "needs-approval")
|
|
278
|
+
return { state: "active", command };
|
|
279
|
+
// A future container/Kubernetes adapter may complete a command without
|
|
280
|
+
// exposing the desktop run shape. Its terminal command is still authoritative.
|
|
281
|
+
return { state: "completed", command };
|
|
282
|
+
}
|
|
283
|
+
export async function finishRemoteAutomationHistory(meta, state, error) {
|
|
284
|
+
if (!meta.remoteAutomationRunId)
|
|
285
|
+
return;
|
|
286
|
+
await finishAutomationRun(meta.remoteAutomationRunId, state === "completed" ? "success" : "error", error);
|
|
287
|
+
}
|
package/dist/jobs/scheduler.js
CHANGED
|
@@ -2,6 +2,7 @@ import { resourceGetByPath, resourceListAllOwners, resourcePutIfCurrent, } from
|
|
|
2
2
|
import { backgroundRunCutOffReason, isBackgroundAutomationRunActive, resolveBackgroundAutomationIdentity, runBackgroundAutomation, } from "./background-automation-runner.js";
|
|
3
3
|
import { nextOccurrence, isValidCron, describeCron, effectiveTimezone, } from "./cron.js";
|
|
4
4
|
import { buildJobResourceContent, jobBelongsToApp, parseJobResource, } from "./frontmatter.js";
|
|
5
|
+
import { dispatchRemoteAutomation, finishRemoteAutomationHistory, getRemoteAutomationStatus, } from "./remote-execution.js";
|
|
5
6
|
import { claimAutomationRun, finishAutomationRun, getAutomationRun, listAutomationRuns, } from "./run-history.js";
|
|
6
7
|
import { acquireAutomationSchedulerLease, recordAutomationSchedulerHealth, releaseAutomationSchedulerLease, renewAutomationSchedulerLease, AUTOMATION_SCHEDULER_LEASE_RENEWAL_MS, } from "./scheduler-health.js";
|
|
7
8
|
// ─── Frontmatter parsing ────────────────────────────────────────────────────
|
|
@@ -150,6 +151,12 @@ async function processRecurringJobsWithLease(deps) {
|
|
|
150
151
|
if (!jobBelongsToApp(meta, deps.appId))
|
|
151
152
|
continue;
|
|
152
153
|
healthOrgIds.add(meta.orgId ?? null);
|
|
154
|
+
// A host-targeted run is reconciled from the durable relay command. It
|
|
155
|
+
// must never fall back to this scheduler after the laptop disconnects.
|
|
156
|
+
if (meta.lastStatus === "running" && meta.executionHostId) {
|
|
157
|
+
await reconcileRemoteJob(resource, meta, now);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
153
160
|
// Skip disabled or missing schedule
|
|
154
161
|
if (!meta.enabled || !meta.schedule)
|
|
155
162
|
continue;
|
|
@@ -341,6 +348,56 @@ function hasRecentIdentityFailure(meta, now) {
|
|
|
341
348
|
elapsedMs >= 0 &&
|
|
342
349
|
elapsedMs < IDENTITY_FAILURE_RETRY_MS);
|
|
343
350
|
}
|
|
351
|
+
async function reconcileRemoteJob(resource, meta, now) {
|
|
352
|
+
const ownerEmail = meta.createdBy?.trim() || resource.owner;
|
|
353
|
+
try {
|
|
354
|
+
const remote = await getRemoteAutomationStatus({
|
|
355
|
+
meta,
|
|
356
|
+
ownerEmail,
|
|
357
|
+
orgId: meta.orgId,
|
|
358
|
+
now,
|
|
359
|
+
});
|
|
360
|
+
if (remote.state === "active")
|
|
361
|
+
return;
|
|
362
|
+
const error = remote.error ??
|
|
363
|
+
(remote.state === "failed"
|
|
364
|
+
? "The remote execution host did not complete this automation."
|
|
365
|
+
: undefined);
|
|
366
|
+
await finishRemoteAutomationHistory(meta, remote.state === "completed" ? "completed" : "failed", error).catch((historyError) => {
|
|
367
|
+
console.warn(`[recurring-jobs] Could not finish remote history for "${resource.path}":`, historyError);
|
|
368
|
+
});
|
|
369
|
+
await recordExecutionOutcome(resource, {
|
|
370
|
+
lastRun: meta.lastRun,
|
|
371
|
+
lastStatus: remote.state === "completed" ? "success" : "error",
|
|
372
|
+
lastError: error,
|
|
373
|
+
remoteRequestId: undefined,
|
|
374
|
+
remoteCommandId: undefined,
|
|
375
|
+
remoteRunId: undefined,
|
|
376
|
+
remoteAutomationRunId: undefined,
|
|
377
|
+
remoteAdvanceSchedule: undefined,
|
|
378
|
+
advanceSchedule: meta.remoteAdvanceSchedule !== false,
|
|
379
|
+
});
|
|
380
|
+
console.log(`[recurring-jobs] Remote job "${resource.path}" reached ${remote.state}.`);
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
const message = error instanceof Error
|
|
384
|
+
? error.message.slice(0, 200)
|
|
385
|
+
: "Remote execution host could not be reached.";
|
|
386
|
+
await finishRemoteAutomationHistory(meta, "failed", message).catch(() => undefined);
|
|
387
|
+
await recordExecutionOutcome(resource, {
|
|
388
|
+
lastRun: meta.lastRun,
|
|
389
|
+
lastStatus: "error",
|
|
390
|
+
lastError: message,
|
|
391
|
+
remoteRequestId: undefined,
|
|
392
|
+
remoteCommandId: undefined,
|
|
393
|
+
remoteRunId: undefined,
|
|
394
|
+
remoteAutomationRunId: undefined,
|
|
395
|
+
remoteAdvanceSchedule: undefined,
|
|
396
|
+
advanceSchedule: meta.remoteAdvanceSchedule !== false,
|
|
397
|
+
});
|
|
398
|
+
console.error(`[recurring-jobs] Remote job "${resource.path}" failed:`, message);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
344
401
|
async function executeJob(resource, meta, body, deps, now, options = {}) {
|
|
345
402
|
const jobName = resource.path.replace(/^jobs\//, "").replace(/\.md$/, "");
|
|
346
403
|
const jobContext = {
|
|
@@ -384,6 +441,50 @@ async function executeJob(resource, meta, body, deps, now, options = {}) {
|
|
|
384
441
|
error: "The automation changed before the run could start.",
|
|
385
442
|
};
|
|
386
443
|
}
|
|
444
|
+
const prompt = options.manual
|
|
445
|
+
? `[Manual Automation Run: ${jobName}]\nThis run was explicitly started by the automation owner. Execute the following instructions now:\n\n${body}`
|
|
446
|
+
: `[Recurring Job: ${jobName}]\nSchedule: ${describeCron(meta.schedule, effectiveTimezone(meta.timezone))}\n\nExecute the following job instructions:\n\n${body}`;
|
|
447
|
+
if (meta.executionHostId) {
|
|
448
|
+
try {
|
|
449
|
+
const dispatch = await dispatchRemoteAutomation({
|
|
450
|
+
resource,
|
|
451
|
+
meta,
|
|
452
|
+
body,
|
|
453
|
+
ownerEmail: jobUserEmail,
|
|
454
|
+
orgId: jobOrgId,
|
|
455
|
+
appId: deps.appId,
|
|
456
|
+
prompt,
|
|
457
|
+
title: `${options.manual ? "Automation" : "Job"}: ${jobName}`,
|
|
458
|
+
historyId: options.historyId,
|
|
459
|
+
advanceSchedule: options.advanceSchedule,
|
|
460
|
+
now,
|
|
461
|
+
});
|
|
462
|
+
console.log(`[recurring-jobs] Job "${jobName}" queued on remote host as ${dispatch.command.id}.`);
|
|
463
|
+
return { status: "success", runId: dispatch.command.id };
|
|
464
|
+
}
|
|
465
|
+
catch (err) {
|
|
466
|
+
const lastError = err instanceof Error
|
|
467
|
+
? err.message.slice(0, 200)
|
|
468
|
+
: "Remote dispatch failed";
|
|
469
|
+
const reportedError = `${lastError}. No delivery was confirmed.`;
|
|
470
|
+
await recordExecutionOutcome(resource, {
|
|
471
|
+
lastRun: meta.lastRun,
|
|
472
|
+
lastStatus: "error",
|
|
473
|
+
lastError: reportedError,
|
|
474
|
+
remoteRequestId: undefined,
|
|
475
|
+
remoteCommandId: undefined,
|
|
476
|
+
remoteRunId: undefined,
|
|
477
|
+
remoteAutomationRunId: undefined,
|
|
478
|
+
remoteAdvanceSchedule: undefined,
|
|
479
|
+
advanceSchedule: options.advanceSchedule,
|
|
480
|
+
});
|
|
481
|
+
if (options.historyId) {
|
|
482
|
+
await finishAutomationRun(options.historyId, "error", reportedError);
|
|
483
|
+
}
|
|
484
|
+
console.error(`[recurring-jobs] Job "${jobName}" remote dispatch failed:`, reportedError);
|
|
485
|
+
return { status: "error", error: reportedError };
|
|
486
|
+
}
|
|
487
|
+
}
|
|
387
488
|
const requestContext = meta.originScopeId && meta.deliveryPlatform && meta.deliveryDestination
|
|
388
489
|
? {
|
|
389
490
|
isIntegrationCaller: true,
|
|
@@ -413,9 +514,7 @@ async function executeJob(resource, meta, body, deps, now, options = {}) {
|
|
|
413
514
|
automation: jobContext,
|
|
414
515
|
ownerEmail: jobUserEmail,
|
|
415
516
|
orgId: jobOrgId,
|
|
416
|
-
prompt
|
|
417
|
-
? `[Manual Automation Run: ${jobName}]\nThis run was explicitly started by the automation owner. Execute the following instructions now:\n\n${body}`
|
|
418
|
-
: `[Recurring Job: ${jobName}]\nSchedule: ${describeCron(meta.schedule, effectiveTimezone(meta.timezone))}\n\nExecute the following job instructions:\n\n${body}`,
|
|
517
|
+
prompt,
|
|
419
518
|
threadTitle: `${options.manual ? "Automation" : "Job"}: ${jobName} — ${now.toLocaleDateString()}`,
|
|
420
519
|
runIdPrefix: `${options.manual ? "manual" : "job"}-${jobName}`,
|
|
421
520
|
usageLabel: `${options.manual ? "manual-automation" : "recurring-job"}:${jobName}`,
|
package/dist/jobs/tools.js
CHANGED
|
@@ -75,7 +75,7 @@ export async function authorizeJobMutation(resourceOwner, meta, appId) {
|
|
|
75
75
|
return "Only the job's creator (or an org admin) can update or delete it.";
|
|
76
76
|
}
|
|
77
77
|
async function runCreate(args, appId) {
|
|
78
|
-
const { name, instructions, scope, runAs, model } = args;
|
|
78
|
+
const { name, instructions, scope, runAs, model, executionHostId, executionEngine, executionCwd, } = args;
|
|
79
79
|
const schedule = typeof args.schedule === "string" && args.schedule.trim()
|
|
80
80
|
? args.schedule.trim()
|
|
81
81
|
: DEFAULT_AUTOMATION_SCHEDULE;
|
|
@@ -136,6 +136,15 @@ async function runCreate(args, appId) {
|
|
|
136
136
|
...(typeof model === "string" && model.trim()
|
|
137
137
|
? { model: model.trim() }
|
|
138
138
|
: {}),
|
|
139
|
+
...(typeof executionHostId === "string" && executionHostId.trim()
|
|
140
|
+
? { executionHostId: executionHostId.trim() }
|
|
141
|
+
: {}),
|
|
142
|
+
...(typeof executionEngine === "string" && executionEngine.trim()
|
|
143
|
+
? { executionEngine: executionEngine.trim() }
|
|
144
|
+
: {}),
|
|
145
|
+
...(typeof executionCwd === "string" && executionCwd.trim()
|
|
146
|
+
? { executionCwd: executionCwd.trim() }
|
|
147
|
+
: {}),
|
|
139
148
|
...(mcpTools?.length ? { mcpTools } : {}),
|
|
140
149
|
};
|
|
141
150
|
const content = buildJobContent(meta, instructions);
|
|
@@ -193,6 +202,9 @@ async function runList(args, appId) {
|
|
|
193
202
|
deliveryPlatform: meta.deliveryPlatform || null,
|
|
194
203
|
deliveryDestination: meta.deliveryDestination || null,
|
|
195
204
|
model: meta.model || null,
|
|
205
|
+
executionHostId: meta.executionHostId || null,
|
|
206
|
+
executionEngine: meta.executionEngine || null,
|
|
207
|
+
executionCwd: meta.executionCwd || null,
|
|
196
208
|
mcpTools: meta.mcpTools || [],
|
|
197
209
|
};
|
|
198
210
|
}));
|
|
@@ -203,7 +215,7 @@ async function runList(args, appId) {
|
|
|
203
215
|
return JSON.stringify(scheduledJobs, null, 2);
|
|
204
216
|
}
|
|
205
217
|
async function runUpdate(args, appId) {
|
|
206
|
-
const { name, schedule, instructions, enabled, scope, runAs, model } = args;
|
|
218
|
+
const { name, schedule, instructions, enabled, scope, runAs, model, executionHostId, executionEngine, executionCwd, } = args;
|
|
207
219
|
const path = `jobs/${name}.md`;
|
|
208
220
|
// Try to find the resource
|
|
209
221
|
let resource = await resourceGetByPath(getSharedOwner(), path);
|
|
@@ -260,6 +272,24 @@ async function runUpdate(args, appId) {
|
|
|
260
272
|
}
|
|
261
273
|
if (typeof model === "string" && model.trim())
|
|
262
274
|
meta.model = model.trim();
|
|
275
|
+
if (executionHostId !== undefined) {
|
|
276
|
+
meta.executionHostId =
|
|
277
|
+
typeof executionHostId === "string" && executionHostId.trim()
|
|
278
|
+
? executionHostId.trim()
|
|
279
|
+
: undefined;
|
|
280
|
+
}
|
|
281
|
+
if (executionEngine !== undefined) {
|
|
282
|
+
meta.executionEngine =
|
|
283
|
+
typeof executionEngine === "string" && executionEngine.trim()
|
|
284
|
+
? executionEngine.trim()
|
|
285
|
+
: undefined;
|
|
286
|
+
}
|
|
287
|
+
if (executionCwd !== undefined) {
|
|
288
|
+
meta.executionCwd =
|
|
289
|
+
typeof executionCwd === "string" && executionCwd.trim()
|
|
290
|
+
? executionCwd.trim()
|
|
291
|
+
: undefined;
|
|
292
|
+
}
|
|
263
293
|
if (args.mcpTools !== undefined) {
|
|
264
294
|
try {
|
|
265
295
|
const mcpTools = normalizeJobMcpTools(args.mcpTools) ?? [];
|
|
@@ -284,6 +314,9 @@ async function runUpdate(args, appId) {
|
|
|
284
314
|
enabled: meta.enabled,
|
|
285
315
|
nextRun: meta.nextRun,
|
|
286
316
|
mcpTools: meta.mcpTools || [],
|
|
317
|
+
executionHostId: meta.executionHostId || null,
|
|
318
|
+
executionEngine: meta.executionEngine || null,
|
|
319
|
+
executionCwd: meta.executionCwd || null,
|
|
287
320
|
});
|
|
288
321
|
}
|
|
289
322
|
async function runDelete(args, appId) {
|
|
@@ -326,7 +359,9 @@ Actions:
|
|
|
326
359
|
|
|
327
360
|
Cron format is 5 fields: minute hour day-of-month month day-of-week. Common patterns: '0 9 * * *' (daily 9am), '0 9 * * 1-5' (weekdays 9am), '0 * * * *' (every hour), '0 9 * * 1' (Mondays 9am), '*/30 * * * *' (every 30 min).
|
|
328
361
|
|
|
329
|
-
For jobs that use a connected MCP, pass the exact tool names in mcpTools. This binds only those tools to the background run; OAuth credentials remain in the connector and are resolved for the job's user/org context
|
|
362
|
+
For jobs that use a connected MCP, pass the exact tool names in mcpTools. This binds only those tools to the background run; OAuth credentials remain in the connector and are resolved for the job's user/org context.
|
|
363
|
+
|
|
364
|
+
To run code-agent work on a paired always-on computer, pass executionHostId (from manage-automations action=list-hosts), and optionally executionEngine and executionCwd. The selected host is explicit and never silently replaced.`,
|
|
330
365
|
parameters: {
|
|
331
366
|
type: "object",
|
|
332
367
|
properties: {
|
|
@@ -370,6 +405,18 @@ For jobs that use a connected MCP, pass the exact tool names in mcpTools. This b
|
|
|
370
405
|
type: "string",
|
|
371
406
|
description: "Optional model id for this routine. The channel/app/engine default is used when omitted.",
|
|
372
407
|
},
|
|
408
|
+
executionHostId: {
|
|
409
|
+
type: "string",
|
|
410
|
+
description: "Optional exact paired execution host id. Use manage-automations action=list-hosts first.",
|
|
411
|
+
},
|
|
412
|
+
executionEngine: {
|
|
413
|
+
type: "string",
|
|
414
|
+
description: "Optional host engine id, for example codex-cli or claude-cli.",
|
|
415
|
+
},
|
|
416
|
+
executionCwd: {
|
|
417
|
+
type: "string",
|
|
418
|
+
description: "Optional workspace path on the selected host; otherwise the connector's configured workspace is used.",
|
|
419
|
+
},
|
|
373
420
|
mcpTools: {
|
|
374
421
|
type: "array",
|
|
375
422
|
items: { type: "string" },
|
|
@@ -273,10 +273,14 @@ const messages = {
|
|
|
273
273
|
"errorMessages.startNewChat": "بدء محادثة جديدة",
|
|
274
274
|
"errorMessages.upgradeAtBuilder": "الترقية عبر Builder.io",
|
|
275
275
|
"feedback.inaccurate": "غير دقيق",
|
|
276
|
+
"feedback.keyboardHint": "{{shortcut}} Enter للإرسال",
|
|
276
277
|
"feedback.notHelpful": "غير مفيد",
|
|
278
|
+
"feedback.placeholder": "أخبرنا بما حدث من خطأ...",
|
|
279
|
+
"feedback.submit": "إرسال",
|
|
277
280
|
"feedback.thumbsDown": "عدم إعجاب",
|
|
278
281
|
"feedback.thumbsUp": "إعجاب",
|
|
279
282
|
"feedback.tooSlow": "بطيء جدًا",
|
|
283
|
+
"feedback.whatWentWrong": "ما الذي حدث بشكل خاطئ؟",
|
|
280
284
|
"feedback.wrongTool": "أداة غير صحيحة",
|
|
281
285
|
"header.switchToCli": "التبديل إلى CLI",
|
|
282
286
|
"history.active": "نشطة",
|
|
@@ -401,10 +401,14 @@ const messages = {
|
|
|
401
401
|
"errorMessages.startNewChat": "Neuen Chat starten",
|
|
402
402
|
"errorMessages.upgradeAtBuilder": "Bei Builder.io upgraden",
|
|
403
403
|
"feedback.inaccurate": "Ungenau",
|
|
404
|
+
"feedback.keyboardHint": "{{shortcut}} Enter zum Senden",
|
|
404
405
|
"feedback.notHelpful": "Nicht hilfreich",
|
|
406
|
+
"feedback.placeholder": "Sag uns, was schiefgelaufen ist...",
|
|
407
|
+
"feedback.submit": "Senden",
|
|
405
408
|
"feedback.thumbsDown": "Daumen runter",
|
|
406
409
|
"feedback.thumbsUp": "Daumen hoch",
|
|
407
410
|
"feedback.tooSlow": "Zu langsam",
|
|
411
|
+
"feedback.whatWentWrong": "Was ist schiefgelaufen?",
|
|
408
412
|
"feedback.wrongTool": "Falsches Tool",
|
|
409
413
|
"contextMeter.ariaLabel": "Kontext {{percent}} %, {{totalTokens}}{{breakdown}}. Kontextanalyse öffnen.",
|
|
410
414
|
"contextMeter.breakdown": " gesamt: {{systemTokens}} System + {{conversationTokens}} Unterhaltung",
|
|
@@ -265,10 +265,14 @@ declare const messages: {
|
|
|
265
265
|
readonly "errorMessages.startNewChat": "Start new chat";
|
|
266
266
|
readonly "errorMessages.upgradeAtBuilder": "Upgrade at Builder.io";
|
|
267
267
|
readonly "feedback.inaccurate": "Inaccurate";
|
|
268
|
+
readonly "feedback.keyboardHint": "{{shortcut}} Enter to send";
|
|
268
269
|
readonly "feedback.notHelpful": "Not helpful";
|
|
270
|
+
readonly "feedback.placeholder": "Tell us what went wrong...";
|
|
271
|
+
readonly "feedback.submit": "Submit";
|
|
269
272
|
readonly "feedback.thumbsDown": "Thumbs down";
|
|
270
273
|
readonly "feedback.thumbsUp": "Thumbs up";
|
|
271
274
|
readonly "feedback.tooSlow": "Too slow";
|
|
275
|
+
readonly "feedback.whatWentWrong": "What went wrong?";
|
|
272
276
|
readonly "feedback.wrongTool": "Wrong tool";
|
|
273
277
|
readonly "header.switchToCli": "Switch to CLI";
|
|
274
278
|
readonly "history.active": "Active";
|
|
@@ -265,10 +265,14 @@ const messages = {
|
|
|
265
265
|
"errorMessages.startNewChat": "Start new chat",
|
|
266
266
|
"errorMessages.upgradeAtBuilder": "Upgrade at Builder.io",
|
|
267
267
|
"feedback.inaccurate": "Inaccurate",
|
|
268
|
+
"feedback.keyboardHint": "{{shortcut}} Enter to send",
|
|
268
269
|
"feedback.notHelpful": "Not helpful",
|
|
270
|
+
"feedback.placeholder": "Tell us what went wrong...",
|
|
271
|
+
"feedback.submit": "Submit",
|
|
269
272
|
"feedback.thumbsDown": "Thumbs down",
|
|
270
273
|
"feedback.thumbsUp": "Thumbs up",
|
|
271
274
|
"feedback.tooSlow": "Too slow",
|
|
275
|
+
"feedback.whatWentWrong": "What went wrong?",
|
|
272
276
|
"feedback.wrongTool": "Wrong tool",
|
|
273
277
|
"header.switchToCli": "Switch to CLI",
|
|
274
278
|
"history.active": "Active",
|
|
@@ -408,10 +408,14 @@ const messages = {
|
|
|
408
408
|
"errorMessages.startNewChat": "Iniciar un chat nuevo",
|
|
409
409
|
"errorMessages.upgradeAtBuilder": "Mejorar el plan en Builder.io",
|
|
410
410
|
"feedback.inaccurate": "Impreciso",
|
|
411
|
+
"feedback.keyboardHint": "{{shortcut}} Enter para enviar",
|
|
411
412
|
"feedback.notHelpful": "Poco útil",
|
|
413
|
+
"feedback.placeholder": "Cuéntanos qué salió mal...",
|
|
414
|
+
"feedback.submit": "Enviar",
|
|
412
415
|
"feedback.thumbsDown": "No me gusta",
|
|
413
416
|
"feedback.thumbsUp": "Me gusta",
|
|
414
417
|
"feedback.tooSlow": "Demasiado lento",
|
|
418
|
+
"feedback.whatWentWrong": "¿Qué salió mal?",
|
|
415
419
|
"feedback.wrongTool": "Herramienta incorrecta",
|
|
416
420
|
"contextMeter.ariaLabel": "Contexto {{percent}} %, {{totalTokens}}{{breakdown}}. Abrir el análisis de contexto.",
|
|
417
421
|
"contextMeter.breakdown": " en total: {{systemTokens}} del sistema + {{conversationTokens}} de la conversación",
|
|
@@ -408,10 +408,14 @@ const messages = {
|
|
|
408
408
|
"errorMessages.startNewChat": "Démarrer une nouvelle discussion",
|
|
409
409
|
"errorMessages.upgradeAtBuilder": "Mettre à niveau sur Builder.io",
|
|
410
410
|
"feedback.inaccurate": "Inexact",
|
|
411
|
+
"feedback.keyboardHint": "{{shortcut}} Entrée pour envoyer",
|
|
411
412
|
"feedback.notHelpful": "Peu utile",
|
|
413
|
+
"feedback.placeholder": "Dites-nous ce qui n'a pas fonctionné...",
|
|
414
|
+
"feedback.submit": "Envoyer",
|
|
412
415
|
"feedback.thumbsDown": "Pouce vers le bas",
|
|
413
416
|
"feedback.thumbsUp": "Pouce vers le haut",
|
|
414
417
|
"feedback.tooSlow": "Trop lent",
|
|
418
|
+
"feedback.whatWentWrong": "Qu'est-ce qui n'a pas fonctionné ?",
|
|
415
419
|
"feedback.wrongTool": "Mauvais outil",
|
|
416
420
|
"contextMeter.ariaLabel": "Contexte {{percent}} %, {{totalTokens}}{{breakdown}}. Ouvrir l’analyse du contexte.",
|
|
417
421
|
"contextMeter.breakdown": " au total : {{systemTokens}} système + {{conversationTokens}} conversation",
|
|
@@ -265,10 +265,14 @@ const messages = {
|
|
|
265
265
|
"errorMessages.startNewChat": "नई चैट शुरू करें",
|
|
266
266
|
"errorMessages.upgradeAtBuilder": "Builder.io पर अपग्रेड करें",
|
|
267
267
|
"feedback.inaccurate": "गलत जानकारी",
|
|
268
|
+
"feedback.keyboardHint": "भेजने के लिए {{shortcut}} Enter",
|
|
268
269
|
"feedback.notHelpful": "उपयोगी नहीं",
|
|
270
|
+
"feedback.placeholder": "बताएं कि क्या गलत हुआ...",
|
|
271
|
+
"feedback.submit": "जमा करें",
|
|
269
272
|
"feedback.thumbsDown": "नापसंद",
|
|
270
273
|
"feedback.thumbsUp": "पसंद",
|
|
271
274
|
"feedback.tooSlow": "बहुत धीमा",
|
|
275
|
+
"feedback.whatWentWrong": "क्या गलत हुआ?",
|
|
272
276
|
"feedback.wrongTool": "गलत टूल",
|
|
273
277
|
"header.switchToCli": "CLI पर जाएँ",
|
|
274
278
|
"history.active": "सक्रिय",
|
|
@@ -263,10 +263,14 @@ const messages = {
|
|
|
263
263
|
"errorMessages.startNewChat": "新しいチャットを開始",
|
|
264
264
|
"errorMessages.upgradeAtBuilder": "Builder.io でアップグレード",
|
|
265
265
|
"feedback.inaccurate": "不正確",
|
|
266
|
+
"feedback.keyboardHint": "{{shortcut}} Enter で送信",
|
|
266
267
|
"feedback.notHelpful": "役に立たない",
|
|
268
|
+
"feedback.placeholder": "問題点を教えてください…",
|
|
269
|
+
"feedback.submit": "送信",
|
|
267
270
|
"feedback.thumbsDown": "低評価",
|
|
268
271
|
"feedback.thumbsUp": "高評価",
|
|
269
272
|
"feedback.tooSlow": "遅すぎる",
|
|
273
|
+
"feedback.whatWentWrong": "何が問題でしたか?",
|
|
270
274
|
"feedback.wrongTool": "ツールが不適切",
|
|
271
275
|
"header.switchToCli": "CLI に切り替え",
|
|
272
276
|
"history.active": "アクティブ",
|