@lobu/cli 6.1.1 → 7.1.0
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/dist/commands/_lib/apply/apply-cmd.d.ts +36 -0
- package/dist/commands/_lib/apply/apply-cmd.d.ts.map +1 -1
- package/dist/commands/_lib/apply/apply-cmd.js +696 -40
- package/dist/commands/_lib/apply/apply-cmd.js.map +1 -1
- package/dist/commands/_lib/apply/client.d.ts +285 -0
- package/dist/commands/_lib/apply/client.d.ts.map +1 -1
- package/dist/commands/_lib/apply/client.js +469 -28
- package/dist/commands/_lib/apply/client.js.map +1 -1
- package/dist/commands/_lib/apply/desired-state.d.ts +187 -3
- package/dist/commands/_lib/apply/desired-state.d.ts.map +1 -1
- package/dist/commands/_lib/apply/desired-state.js +879 -88
- package/dist/commands/_lib/apply/desired-state.js.map +1 -1
- package/dist/commands/_lib/apply/diff.d.ts +72 -3
- package/dist/commands/_lib/apply/diff.d.ts.map +1 -1
- package/dist/commands/_lib/apply/diff.js +473 -84
- package/dist/commands/_lib/apply/diff.js.map +1 -1
- package/dist/commands/_lib/apply/prompt.d.ts +6 -0
- package/dist/commands/_lib/apply/prompt.d.ts.map +1 -1
- package/dist/commands/_lib/apply/prompt.js +16 -0
- package/dist/commands/_lib/apply/prompt.js.map +1 -1
- package/dist/commands/_lib/apply/render.d.ts +9 -0
- package/dist/commands/_lib/apply/render.d.ts.map +1 -1
- package/dist/commands/_lib/apply/render.js +80 -3
- package/dist/commands/_lib/apply/render.js.map +1 -1
- package/dist/commands/_lib/connector-loader.d.ts +3 -0
- package/dist/commands/_lib/connector-loader.d.ts.map +1 -0
- package/dist/commands/_lib/connector-loader.js +129 -0
- package/dist/commands/_lib/connector-loader.js.map +1 -0
- package/dist/commands/_lib/connector-run-cmd.d.ts +35 -0
- package/dist/commands/_lib/connector-run-cmd.d.ts.map +1 -0
- package/dist/commands/_lib/connector-run-cmd.js +351 -0
- package/dist/commands/_lib/connector-run-cmd.js.map +1 -0
- package/dist/commands/_lib/export/export-cmd.d.ts +35 -0
- package/dist/commands/_lib/export/export-cmd.d.ts.map +1 -0
- package/dist/commands/_lib/export/export-cmd.js +329 -0
- package/dist/commands/_lib/export/export-cmd.js.map +1 -0
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +11 -14
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +28 -7
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/connector.d.ts +3 -0
- package/dist/commands/connector.d.ts.map +1 -0
- package/dist/commands/connector.js +5 -0
- package/dist/commands/connector.js.map +1 -0
- package/dist/commands/dev.d.ts +23 -0
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +273 -8
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +2 -3
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/eval.d.ts.map +1 -1
- package/dist/commands/eval.js +28 -18
- package/dist/commands/eval.js.map +1 -1
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +29 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +22 -16
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/memory/_lib/browser-auth-cmd.d.ts.map +1 -1
- package/dist/commands/memory/_lib/browser-auth-cmd.js +15 -144
- package/dist/commands/memory/_lib/browser-auth-cmd.js.map +1 -1
- package/dist/commands/memory/_lib/schema.d.ts +28 -1
- package/dist/commands/memory/_lib/schema.d.ts.map +1 -1
- package/dist/commands/memory/_lib/schema.js +120 -4
- package/dist/commands/memory/_lib/schema.js.map +1 -1
- package/dist/commands/memory/_lib/seed-cmd.d.ts.map +1 -1
- package/dist/commands/memory/_lib/seed-cmd.js +41 -18
- package/dist/commands/memory/_lib/seed-cmd.js.map +1 -1
- package/dist/commands/org.d.ts +4 -0
- package/dist/commands/org.d.ts.map +1 -1
- package/dist/commands/org.js +10 -0
- package/dist/commands/org.js.map +1 -1
- package/dist/commands/token.d.ts +9 -0
- package/dist/commands/token.d.ts.map +1 -1
- package/dist/commands/token.js +54 -3
- package/dist/commands/token.js.map +1 -1
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +4 -13
- package/dist/commands/validate.js.map +1 -1
- package/dist/config/loader.js +2 -2
- package/dist/config/loader.js.map +1 -1
- package/dist/connectors/README.md +2 -3
- package/dist/connectors/apple_health.ts +138 -0
- package/dist/connectors/apple_photos.ts +178 -0
- package/dist/connectors/apple_screen_time.ts +82 -0
- package/dist/connectors/browser/evaluate.ts +120 -0
- package/dist/connectors/browser/fill_form.ts +107 -0
- package/dist/connectors/browser/page_text.ts +108 -0
- package/dist/connectors/browser-scraper-utils.ts +111 -3
- package/dist/connectors/capterra.ts +5 -1
- package/dist/connectors/chrome_tabs.ts +74 -0
- package/dist/connectors/g2.ts +5 -1
- package/dist/connectors/github.ts +16 -38
- package/dist/connectors/glassdoor.ts +5 -1
- package/dist/connectors/google_calendar.ts +28 -6
- package/dist/connectors/google_gmail.ts +6 -3
- package/dist/connectors/google_play.ts +32 -5
- package/dist/connectors/hackernews.ts +37 -2
- package/dist/connectors/index.ts +14 -1
- package/dist/connectors/linkedin.ts +32 -9
- package/dist/connectors/local_directory.ts +91 -0
- package/dist/connectors/reddit.ts +1 -0
- package/dist/connectors/revolut.ts +569 -0
- package/dist/connectors/rss.ts +33 -8
- package/dist/connectors/trustpilot.ts +36 -21
- package/dist/connectors/website.ts +8 -69
- package/dist/connectors/whatsapp.ts +21 -22
- package/dist/connectors/whatsapp_local.ts +125 -0
- package/dist/connectors/x.ts +17 -7
- package/dist/db/migrations/20260510220000_connector_required_capability.sql +47 -0
- package/dist/db/migrations/20260512000000_device_worker_connection_binding.sql +113 -0
- package/dist/db/migrations/20260512131703_connections_slug.sql +131 -0
- package/dist/db/migrations/20260513000000_chat_user_identities.sql +24 -0
- package/dist/db/migrations/20260513120000_auth_profiles_device_binding.sql +50 -0
- package/dist/db/migrations/20260513150000_auth_profiles_cdp_url.sql +43 -0
- package/dist/db/migrations/20260513200000_notifications_as_events.sql +86 -0
- package/dist/db/migrations/20260514000000_scheduled_jobs.sql +97 -0
- package/dist/db/migrations/20260514120000_auth_profiles_connector_key_nullable.sql +42 -0
- package/dist/db/migrations/20260514130000_connection_action_modes.sql +103 -0
- package/dist/db/migrations/20260514160000_auth_profiles_mirror_mode.sql +32 -0
- package/dist/db/migrations/20260515120000_agents_per_org_pk.sql +66 -0
- package/dist/db/migrations/20260515150000_geo_enrichment.sql +208 -0
- package/dist/db/migrations/20260515160000_drop_agents_org_id_unique.sql +24 -0
- package/dist/db/migrations/20260515170000_auth_profiles_default_for_connector.sql +23 -0
- package/dist/db/migrations/20260516120000_agents_per_org_pk_swap.sql +125 -0
- package/dist/db/migrations/20260516200000_events_search_tsv.sql +134 -0
- package/dist/db/migrations/20260516200100_events_lifecycle_changes_index.sql +25 -0
- package/dist/db/migrations/20260517010000_drop_unused_indexes.sql +49 -0
- package/dist/db/migrations/20260517020000_softdelete_orphan_feeds.sql +56 -0
- package/dist/db/migrations/20260517030000_pat_worker_id_binding.sql +27 -0
- package/dist/db/migrations/20260517040000_archive_orphan_watchers.sql +30 -0
- package/dist/db/migrations/20260517050000_watcher_agent_id_not_null.sql +34 -0
- package/dist/db/migrations/20260517060000_watcher_schema_additions.sql +78 -0
- package/dist/db/migrations/20260517150000_goals_primitive.sql +55 -0
- package/dist/db/migrations/20260517160000_drop_goals_primitive.sql +45 -0
- package/dist/db/migrations/20260518000000_pending_interactions.sql +49 -0
- package/dist/db/migrations/20260518010000_runs_heartbeat_reaper_index.sql +22 -0
- package/dist/eval/client.d.ts.map +1 -1
- package/dist/eval/client.js +11 -0
- package/dist/eval/client.js.map +1 -1
- package/dist/eval/grader.js +2 -1
- package/dist/eval/grader.js.map +1 -1
- package/dist/eval/types.d.ts +2 -0
- package/dist/eval/types.d.ts.map +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +115 -114
- package/dist/index.js.map +1 -1
- package/dist/internal/context.d.ts +9 -0
- package/dist/internal/context.d.ts.map +1 -1
- package/dist/internal/context.js +41 -6
- package/dist/internal/context.js.map +1 -1
- package/dist/internal/credentials.d.ts +5 -0
- package/dist/internal/credentials.d.ts.map +1 -1
- package/dist/internal/credentials.js +75 -1
- package/dist/internal/credentials.js.map +1 -1
- package/dist/internal/gateway-url.d.ts +14 -0
- package/dist/internal/gateway-url.d.ts.map +1 -1
- package/dist/internal/gateway-url.js +19 -0
- package/dist/internal/gateway-url.js.map +1 -1
- package/dist/internal/index.d.ts +1 -1
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +1 -1
- package/dist/internal/index.js.map +1 -1
- package/dist/internal/local-env.d.ts.map +1 -1
- package/dist/internal/local-env.js +9 -2
- package/dist/internal/local-env.js.map +1 -1
- package/dist/server.bundle.mjs +42251 -36931
- package/dist/start-local.bundle.mjs +16437 -9882
- package/dist/templates/TESTING.md.tmpl +9 -9
- package/package.json +8 -6
- package/dist/connectors/google_photos.ts +0 -776
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ValidationError } from "../../memory/_lib/errors.js";
|
|
1
2
|
// ── Equality helpers ───────────────────────────────────────────────────────
|
|
2
3
|
/**
|
|
3
4
|
* Stable structural equality for JSON-shaped values. Sorts object keys before
|
|
@@ -25,35 +26,68 @@ function canonical(value) {
|
|
|
25
26
|
}
|
|
26
27
|
return JSON.stringify(value);
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
29
|
+
/** `(a ?? "") !== (b ?? "")` — the canonical optional-string comparison. */
|
|
30
|
+
function stringChanged(a, b) {
|
|
31
|
+
return (a ?? "") !== (b ?? "");
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The shared create / noop / update shape behind most `diffX` functions.
|
|
35
|
+
* `extras` is merged into every row (create/noop/update); `updateExtras`
|
|
36
|
+
* adds verb-specific props derived from the changed-field list (e.g.
|
|
37
|
+
* `willRestart`). `changedFields` is attached automatically on update.
|
|
38
|
+
*/
|
|
39
|
+
function buildDiffRow(opts) {
|
|
40
|
+
const extras = opts.extras ?? {};
|
|
41
|
+
if (!opts.remote) {
|
|
42
|
+
return {
|
|
43
|
+
kind: opts.kind,
|
|
44
|
+
verb: "create",
|
|
45
|
+
id: opts.id,
|
|
46
|
+
desired: opts.desired,
|
|
47
|
+
...extras,
|
|
48
|
+
};
|
|
38
49
|
}
|
|
50
|
+
const remote = opts.remote;
|
|
51
|
+
const changed = opts.fields
|
|
52
|
+
.filter((f) => f.changed(opts.desired, remote))
|
|
53
|
+
.map((f) => f.name);
|
|
39
54
|
if (changed.length === 0) {
|
|
40
55
|
return {
|
|
41
|
-
kind:
|
|
56
|
+
kind: opts.kind,
|
|
42
57
|
verb: "noop",
|
|
43
|
-
id:
|
|
44
|
-
desired,
|
|
58
|
+
id: opts.id,
|
|
59
|
+
desired: opts.desired,
|
|
45
60
|
remote,
|
|
61
|
+
...extras,
|
|
46
62
|
};
|
|
47
63
|
}
|
|
48
64
|
return {
|
|
49
|
-
kind:
|
|
65
|
+
kind: opts.kind,
|
|
50
66
|
verb: "update",
|
|
51
|
-
id:
|
|
52
|
-
desired,
|
|
67
|
+
id: opts.id,
|
|
68
|
+
desired: opts.desired,
|
|
53
69
|
remote,
|
|
54
70
|
changedFields: changed,
|
|
71
|
+
...extras,
|
|
72
|
+
...(opts.updateExtras?.(changed) ?? {}),
|
|
55
73
|
};
|
|
56
74
|
}
|
|
75
|
+
// ── Per-resource diff ──────────────────────────────────────────────────────
|
|
76
|
+
function diffAgent(desired, remote) {
|
|
77
|
+
return buildDiffRow({
|
|
78
|
+
kind: "agent",
|
|
79
|
+
id: desired.agentId,
|
|
80
|
+
desired,
|
|
81
|
+
remote,
|
|
82
|
+
fields: [
|
|
83
|
+
{ name: "name", changed: (d, r) => d.name !== r.name },
|
|
84
|
+
{
|
|
85
|
+
name: "description",
|
|
86
|
+
changed: (d, r) => stringChanged(d.description, r.description),
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
});
|
|
90
|
+
}
|
|
57
91
|
/**
|
|
58
92
|
* Compare desired settings against what's currently stored.
|
|
59
93
|
*
|
|
@@ -117,67 +151,249 @@ function diffSettings(agentId, desired, remote) {
|
|
|
117
151
|
};
|
|
118
152
|
}
|
|
119
153
|
function diffPlatform(agentId, desired, remote) {
|
|
154
|
+
return buildDiffRow({
|
|
155
|
+
kind: "platform",
|
|
156
|
+
id: desired.stableId,
|
|
157
|
+
desired,
|
|
158
|
+
remote,
|
|
159
|
+
extras: remote ? { agentId } : { agentId, willRestart: false },
|
|
160
|
+
fields: [
|
|
161
|
+
{ name: "type", changed: (d, r) => d.type !== r.platform },
|
|
162
|
+
{
|
|
163
|
+
name: "config",
|
|
164
|
+
changed: (d, r) => {
|
|
165
|
+
// The route handler stores `platform` inside `config` for stable-id
|
|
166
|
+
// matching, so a noop round-trip from GET will have an extra
|
|
167
|
+
// `platform` key the CLI never wrote. Strip it before diffing so an
|
|
168
|
+
// unchanged platform doesn't show as drift on every plan.
|
|
169
|
+
const remoteConfig = { ...(r.config ?? {}) };
|
|
170
|
+
delete remoteConfig.platform;
|
|
171
|
+
return !deepEqual(d.config, remoteConfig);
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
updateExtras: (changed) => ({
|
|
176
|
+
willRestart: changed.includes("config") || changed.includes("type"),
|
|
177
|
+
}),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
function diffEntityType(desired, remote) {
|
|
181
|
+
return buildDiffRow({
|
|
182
|
+
kind: "entity-type",
|
|
183
|
+
id: desired.slug,
|
|
184
|
+
desired,
|
|
185
|
+
remote,
|
|
186
|
+
fields: [
|
|
187
|
+
{ name: "name", changed: (d, r) => stringChanged(d.name, r.name) },
|
|
188
|
+
{
|
|
189
|
+
name: "description",
|
|
190
|
+
changed: (d, r) => stringChanged(d.description, r.description),
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: "required",
|
|
194
|
+
changed: (d, r) => !deepEqual(d.required ?? [], r.required ?? []),
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
name: "properties",
|
|
198
|
+
changed: (d, r) => !deepEqual(d.properties, r.properties),
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
function diffRelationshipType(desired, remote) {
|
|
204
|
+
return buildDiffRow({
|
|
205
|
+
kind: "relationship-type",
|
|
206
|
+
id: desired.slug,
|
|
207
|
+
desired,
|
|
208
|
+
remote,
|
|
209
|
+
fields: [
|
|
210
|
+
{ name: "name", changed: (d, r) => stringChanged(d.name, r.name) },
|
|
211
|
+
{
|
|
212
|
+
name: "description",
|
|
213
|
+
changed: (d, r) => stringChanged(d.description, r.description),
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: "rules",
|
|
217
|
+
changed: (d, r) => !deepEqual(d.rules ?? [], r.rules ?? []),
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Watcher drift fields split into two routing categories:
|
|
224
|
+
* - **scalar** lives on the `watchers` row → `manage_watchers update`.
|
|
225
|
+
* - **version-bound** lives on the `watcher_versions` row → must go through
|
|
226
|
+
* `create_version` + `upgrade` (server-side bumps `current_version_id`).
|
|
227
|
+
* The diff returns both lists; apply-cmd routes accordingly.
|
|
228
|
+
*
|
|
229
|
+
* Reaction scripts aren't returned by `list_watchers` (write-only on the row),
|
|
230
|
+
* so we can't compare them — apply always re-pushes when declared (idempotent).
|
|
231
|
+
* Remote watchers without a desired model are reported as drift, never deleted.
|
|
232
|
+
*/
|
|
233
|
+
function diffWatcher(desired, remote) {
|
|
234
|
+
const reactionScriptDeclared = desired.reactionScript !== undefined;
|
|
120
235
|
if (!remote) {
|
|
121
236
|
return {
|
|
122
|
-
kind: "
|
|
237
|
+
kind: "watcher",
|
|
123
238
|
verb: "create",
|
|
124
|
-
id: desired.
|
|
125
|
-
agentId,
|
|
239
|
+
id: desired.slug,
|
|
126
240
|
desired,
|
|
127
|
-
|
|
241
|
+
...(reactionScriptDeclared ? { reactionScriptDeclared: true } : {}),
|
|
128
242
|
};
|
|
129
243
|
}
|
|
130
|
-
const
|
|
131
|
-
if (desired.
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
244
|
+
const scalar = [];
|
|
245
|
+
if ((desired.schedule ?? null) !== (remote.schedule ?? null)) {
|
|
246
|
+
scalar.push("schedule");
|
|
247
|
+
}
|
|
248
|
+
if (desired.agent !== (remote.agent_id ?? "")) {
|
|
249
|
+
scalar.push("agent_id");
|
|
250
|
+
}
|
|
251
|
+
if (desired.deviceWorkerId !== undefined &&
|
|
252
|
+
desired.deviceWorkerId !== (remote.device_worker_id ?? undefined)) {
|
|
253
|
+
scalar.push("device_worker_id");
|
|
254
|
+
}
|
|
255
|
+
if (desired.schedulerClientId !== undefined &&
|
|
256
|
+
desired.schedulerClientId !== (remote.scheduler_client_id ?? undefined)) {
|
|
257
|
+
scalar.push("scheduler_client_id");
|
|
258
|
+
}
|
|
259
|
+
if (desired.notificationChannel !== undefined &&
|
|
260
|
+
desired.notificationChannel !== (remote.notification_channel ?? undefined)) {
|
|
261
|
+
scalar.push("notification_channel");
|
|
262
|
+
}
|
|
263
|
+
if (desired.notificationPriority !== undefined &&
|
|
264
|
+
desired.notificationPriority !== (remote.notification_priority ?? undefined)) {
|
|
265
|
+
scalar.push("notification_priority");
|
|
266
|
+
}
|
|
267
|
+
if (desired.minCooldownSeconds !== undefined &&
|
|
268
|
+
desired.minCooldownSeconds !== (remote.min_cooldown_seconds ?? undefined)) {
|
|
269
|
+
scalar.push("min_cooldown_seconds");
|
|
270
|
+
}
|
|
271
|
+
if (desired.tags !== undefined &&
|
|
272
|
+
!deepEqual(desired.tags, remote.tags ?? [])) {
|
|
273
|
+
scalar.push("tags");
|
|
274
|
+
}
|
|
275
|
+
if (desired.agentKind !== undefined &&
|
|
276
|
+
desired.agentKind !== (remote.agent_kind ?? undefined)) {
|
|
277
|
+
scalar.push("agent_kind");
|
|
278
|
+
}
|
|
279
|
+
const versionBound = [];
|
|
280
|
+
if (desired.prompt !== (remote.prompt ?? "")) {
|
|
281
|
+
versionBound.push("prompt");
|
|
282
|
+
}
|
|
283
|
+
if (!deepEqual(desired.extractionSchema ?? {}, remote.extraction_schema ?? {})) {
|
|
284
|
+
versionBound.push("extraction_schema");
|
|
285
|
+
}
|
|
286
|
+
// Sources live on the watchers row but are written as part of create_version
|
|
287
|
+
// when changed (server copies them to the version's per-assignment scope).
|
|
288
|
+
// Diff against `remote.sources` (also from the row) and route through
|
|
289
|
+
// create_version so the version chain stays consistent.
|
|
290
|
+
if (desired.sources !== undefined &&
|
|
291
|
+
!deepEqual(desired.sources, remote.sources ?? [])) {
|
|
292
|
+
versionBound.push("sources");
|
|
293
|
+
}
|
|
294
|
+
if (desired.reactionsGuidance !== undefined &&
|
|
295
|
+
desired.reactionsGuidance !== (remote.reactions_guidance ?? "")) {
|
|
296
|
+
versionBound.push("reactions_guidance");
|
|
297
|
+
}
|
|
298
|
+
if (desired.jsonTemplate !== undefined &&
|
|
299
|
+
!deepEqual(desired.jsonTemplate, remote.json_template)) {
|
|
300
|
+
versionBound.push("json_template");
|
|
301
|
+
}
|
|
302
|
+
if (desired.keyingConfig !== undefined &&
|
|
303
|
+
!deepEqual(desired.keyingConfig, remote.keying_config ?? {})) {
|
|
304
|
+
versionBound.push("keying_config");
|
|
305
|
+
}
|
|
306
|
+
if (desired.classifiers !== undefined &&
|
|
307
|
+
!deepEqual(desired.classifiers, remote.classifiers ?? [])) {
|
|
308
|
+
versionBound.push("classifiers");
|
|
309
|
+
}
|
|
310
|
+
if (desired.condensationPrompt !== undefined &&
|
|
311
|
+
desired.condensationPrompt !== (remote.condensation_prompt ?? "")) {
|
|
312
|
+
versionBound.push("condensation_prompt");
|
|
313
|
+
}
|
|
314
|
+
if (desired.condensationWindowCount !== undefined &&
|
|
315
|
+
desired.condensationWindowCount !==
|
|
316
|
+
(remote.condensation_window_count ?? undefined)) {
|
|
317
|
+
versionBound.push("condensation_window_count");
|
|
318
|
+
}
|
|
319
|
+
const changed = [...scalar, ...versionBound];
|
|
320
|
+
if (reactionScriptDeclared)
|
|
321
|
+
changed.push("reaction_script");
|
|
141
322
|
if (changed.length === 0) {
|
|
142
|
-
return {
|
|
143
|
-
kind: "platform",
|
|
144
|
-
verb: "noop",
|
|
145
|
-
id: desired.stableId,
|
|
146
|
-
agentId,
|
|
147
|
-
desired,
|
|
148
|
-
remote,
|
|
149
|
-
};
|
|
323
|
+
return { kind: "watcher", verb: "noop", id: desired.slug, desired, remote };
|
|
150
324
|
}
|
|
151
325
|
return {
|
|
152
|
-
kind: "
|
|
326
|
+
kind: "watcher",
|
|
153
327
|
verb: "update",
|
|
154
|
-
id: desired.
|
|
155
|
-
agentId,
|
|
328
|
+
id: desired.slug,
|
|
156
329
|
desired,
|
|
157
330
|
remote,
|
|
158
331
|
changedFields: changed,
|
|
159
|
-
|
|
332
|
+
...(versionBound.length > 0 ? { versionBoundFields: versionBound } : {}),
|
|
333
|
+
...(reactionScriptDeclared ? { reactionScriptDeclared: true } : {}),
|
|
160
334
|
};
|
|
161
335
|
}
|
|
162
|
-
|
|
336
|
+
// ── Connectors ─────────────────────────────────────────────────────────────
|
|
337
|
+
const INTERACTIVE_AUTH_KINDS = new Set([
|
|
338
|
+
"oauth_account",
|
|
339
|
+
"browser_session",
|
|
340
|
+
]);
|
|
341
|
+
function connectorDefinitionId(def) {
|
|
342
|
+
return def.key ?? def.sourceFile;
|
|
343
|
+
}
|
|
344
|
+
function diffConnectorDefinition(desired, installedKeys) {
|
|
345
|
+
const id = connectorDefinitionId(desired);
|
|
346
|
+
// The CLI can't compare source hashes (the server compiles, and the stored
|
|
347
|
+
// hash is of the *compiled* output) — so we always emit a "sync" row;
|
|
348
|
+
// `install_connector` is idempotent and reports `updated:false` on apply
|
|
349
|
+
// when the code is byte-identical, so this never churns remote state.
|
|
350
|
+
const installedRemotely = desired.key
|
|
351
|
+
? installedKeys.has(desired.key)
|
|
352
|
+
: false;
|
|
353
|
+
return {
|
|
354
|
+
kind: "connector-definition",
|
|
355
|
+
// "update" (re-push) when already installed; `install_connector` is
|
|
356
|
+
// idempotent and reports `updated:false` if the code is unchanged.
|
|
357
|
+
verb: installedRemotely ? "update" : "create",
|
|
358
|
+
id,
|
|
359
|
+
desired,
|
|
360
|
+
installedRemotely,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
function diffAuthProfile(desired, remote) {
|
|
163
364
|
if (!remote) {
|
|
164
|
-
return {
|
|
365
|
+
return {
|
|
366
|
+
kind: "auth-profile",
|
|
367
|
+
verb: "create",
|
|
368
|
+
id: desired.slug,
|
|
369
|
+
desired,
|
|
370
|
+
needsAuth: INTERACTIVE_AUTH_KINDS.has(desired.kind),
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
// `connector` / `kind` are immutable — `update_auth_profile` can't change
|
|
374
|
+
// them, so reusing a slug for a different connector/kind would silently push
|
|
375
|
+
// credentials into the wrong profile. Hard-stop instead.
|
|
376
|
+
if (remote.connector_key !== desired.connector ||
|
|
377
|
+
remote.profile_kind !== desired.kind) {
|
|
378
|
+
throw new ValidationError(`${desired.sourceFile}: auth_profile "${desired.slug}" is bound to ${remote.connector_key}/${remote.profile_kind} remotely, but the manifest declares ${desired.connector}/${desired.kind} — delete it manually or use a new slug`);
|
|
165
379
|
}
|
|
166
380
|
const changed = [];
|
|
167
|
-
if ((desired.name ?? "") !== (remote.
|
|
381
|
+
if ((desired.name ?? "") !== (remote.display_name ?? "")) {
|
|
168
382
|
changed.push("name");
|
|
169
|
-
if ((desired.description ?? "") !== (remote.description ?? "")) {
|
|
170
|
-
changed.push("description");
|
|
171
|
-
}
|
|
172
|
-
if (!deepEqual(desired.required ?? [], remote.required ?? [])) {
|
|
173
|
-
changed.push("required");
|
|
174
383
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
384
|
+
// Credentials can't be read back from the server (write-only secrets). For
|
|
385
|
+
// non-interactive kinds with declared credentials, always re-push them
|
|
386
|
+
// (idempotent) so rotated secrets propagate — show as a redacted "credentials"
|
|
387
|
+
// change. Interactive kinds never carry credentials in the manifest.
|
|
388
|
+
const declaresCredentials = !INTERACTIVE_AUTH_KINDS.has(desired.kind) &&
|
|
389
|
+
desired.credentials !== undefined &&
|
|
390
|
+
Object.keys(desired.credentials).length > 0;
|
|
391
|
+
if (declaresCredentials)
|
|
392
|
+
changed.push("credentials");
|
|
393
|
+
const needsAuth = INTERACTIVE_AUTH_KINDS.has(desired.kind) && remote.status !== "active";
|
|
394
|
+
if (changed.length === 0 && !needsAuth) {
|
|
179
395
|
return {
|
|
180
|
-
kind: "
|
|
396
|
+
kind: "auth-profile",
|
|
181
397
|
verb: "noop",
|
|
182
398
|
id: desired.slug,
|
|
183
399
|
desired,
|
|
@@ -185,49 +401,81 @@ function diffEntityType(desired, remote) {
|
|
|
185
401
|
};
|
|
186
402
|
}
|
|
187
403
|
return {
|
|
188
|
-
kind: "
|
|
189
|
-
verb: "update",
|
|
404
|
+
kind: "auth-profile",
|
|
405
|
+
verb: changed.length > 0 ? "update" : "noop",
|
|
190
406
|
id: desired.slug,
|
|
191
407
|
desired,
|
|
192
408
|
remote,
|
|
193
|
-
changedFields: changed,
|
|
409
|
+
...(changed.length > 0 ? { changedFields: changed } : {}),
|
|
410
|
+
...(needsAuth ? { needsAuth: true } : {}),
|
|
194
411
|
};
|
|
195
412
|
}
|
|
196
|
-
function
|
|
413
|
+
function diffConnection(desired, remote) {
|
|
197
414
|
if (!remote) {
|
|
198
415
|
return {
|
|
199
|
-
kind: "
|
|
416
|
+
kind: "connection",
|
|
200
417
|
verb: "create",
|
|
201
418
|
id: desired.slug,
|
|
202
419
|
desired,
|
|
203
420
|
};
|
|
204
421
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if (
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
if (!deepEqual(desired.rules ?? [], remote.rules ?? [])) {
|
|
212
|
-
changed.push("rules");
|
|
422
|
+
// `connector` is immutable — `update` can't change `connector_key`, so a
|
|
423
|
+
// slug bound to a different connector remotely must be a hard error, never
|
|
424
|
+
// an "update" that mutates auth/config on the wrong connector.
|
|
425
|
+
if (remote.connector_key !== desired.connector) {
|
|
426
|
+
throw new ValidationError(`${desired.sourceFile}: connection "${desired.slug}" is bound to connector "${remote.connector_key}" remotely, but the manifest declares "${desired.connector}" — delete it manually or use a new slug`);
|
|
213
427
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
kind: "relationship-type",
|
|
217
|
-
verb: "noop",
|
|
218
|
-
id: desired.slug,
|
|
219
|
-
desired,
|
|
220
|
-
remote,
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
return {
|
|
224
|
-
kind: "relationship-type",
|
|
225
|
-
verb: "update",
|
|
428
|
+
return buildDiffRow({
|
|
429
|
+
kind: "connection",
|
|
226
430
|
id: desired.slug,
|
|
227
431
|
desired,
|
|
228
432
|
remote,
|
|
229
|
-
|
|
230
|
-
|
|
433
|
+
fields: [
|
|
434
|
+
{
|
|
435
|
+
name: "name",
|
|
436
|
+
changed: (d, r) => stringChanged(d.name, r.display_name),
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
name: "auth",
|
|
440
|
+
changed: (d, r) => (d.authProfileSlug ?? null) !== (r.auth_profile_slug ?? null),
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
name: "app_auth",
|
|
444
|
+
changed: (d, r) => (d.appAuthProfileSlug ?? null) !== (r.app_auth_profile_slug ?? null),
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
name: "config",
|
|
448
|
+
changed: (d, r) => !deepEqual(d.config ?? {}, r.config ?? {}),
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
name: "device_worker_id",
|
|
452
|
+
changed: (d, r) => (d.deviceWorkerId ?? null) !== (r.device_worker_id ?? null),
|
|
453
|
+
},
|
|
454
|
+
],
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
function diffFeed(connectionSlug, desired, remote) {
|
|
458
|
+
return buildDiffRow({
|
|
459
|
+
kind: "feed",
|
|
460
|
+
id: `${connectionSlug}/${desired.feedKey}`,
|
|
461
|
+
desired,
|
|
462
|
+
remote,
|
|
463
|
+
extras: { connectionSlug },
|
|
464
|
+
fields: [
|
|
465
|
+
{
|
|
466
|
+
name: "name",
|
|
467
|
+
changed: (d, r) => stringChanged(d.name, r.display_name),
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
name: "schedule",
|
|
471
|
+
changed: (d, r) => (d.schedule ?? null) !== (r.schedule ?? null),
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
name: "config",
|
|
475
|
+
changed: (d, r) => !deepEqual(d.config ?? {}, r.config ?? {}),
|
|
476
|
+
},
|
|
477
|
+
],
|
|
478
|
+
});
|
|
231
479
|
}
|
|
232
480
|
export function computeDiff(desired, remote, opts = {}) {
|
|
233
481
|
const rows = [];
|
|
@@ -314,10 +562,151 @@ export function computeDiff(desired, remote, opts = {}) {
|
|
|
314
562
|
});
|
|
315
563
|
}
|
|
316
564
|
}
|
|
565
|
+
const remoteWatcherBySlug = new Map(remote.watchers.map((w) => [w.slug, w]));
|
|
566
|
+
const desiredWatcherSlugs = new Set(desired.watchers.map((w) => w.slug));
|
|
567
|
+
for (const watcher of desired.watchers) {
|
|
568
|
+
rows.push(diffWatcher(watcher, remoteWatcherBySlug.get(watcher.slug)));
|
|
569
|
+
}
|
|
570
|
+
for (const remoteWatcher of remote.watchers) {
|
|
571
|
+
if (!desiredWatcherSlugs.has(remoteWatcher.slug)) {
|
|
572
|
+
rows.push({
|
|
573
|
+
kind: "watcher",
|
|
574
|
+
verb: "drift",
|
|
575
|
+
id: remoteWatcher.slug,
|
|
576
|
+
remote: remoteWatcher,
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
const notes = [];
|
|
582
|
+
// Connectors run only on a full apply (`--only agents|memory` skips them).
|
|
583
|
+
const desiredConnectors = desired.connectors ?? {
|
|
584
|
+
definitions: [],
|
|
585
|
+
authProfiles: [],
|
|
586
|
+
connections: [],
|
|
587
|
+
};
|
|
588
|
+
// Sort remote collections so drift rows + notes render deterministically
|
|
589
|
+
// regardless of server response ordering.
|
|
590
|
+
const remoteConnectorDefinitions = [
|
|
591
|
+
...(remote.connectorDefinitions ?? []),
|
|
592
|
+
].sort((a, b) => a.key.localeCompare(b.key));
|
|
593
|
+
const remoteAuthProfiles = [...(remote.authProfiles ?? [])].sort((a, b) => a.slug.localeCompare(b.slug));
|
|
594
|
+
const remoteConnections = [...(remote.connections ?? [])].sort((a, b) => a.slug.localeCompare(b.slug));
|
|
595
|
+
const remoteFeedsByConnectionId = remote.feedsByConnectionId ?? new Map();
|
|
596
|
+
if (!only) {
|
|
597
|
+
const installedKeys = new Set(remoteConnectorDefinitions.filter((d) => d.installed).map((d) => d.key));
|
|
598
|
+
const declaredKeys = new Set(desiredConnectors.definitions
|
|
599
|
+
.map((d) => d.key)
|
|
600
|
+
.filter((k) => !!k));
|
|
601
|
+
// Connectors referenced by a desired auth profile / connection are (or
|
|
602
|
+
// will be) installed in the org too — bundled ones included — so they
|
|
603
|
+
// aren't "undeclared".
|
|
604
|
+
const referencedConnectorKeys = new Set([
|
|
605
|
+
...desiredConnectors.authProfiles.map((p) => p.connector),
|
|
606
|
+
...desiredConnectors.connections.map((c) => c.connector),
|
|
607
|
+
]);
|
|
608
|
+
// Auto-discovered `.connector.ts` files whose key the CLI can't know up
|
|
609
|
+
// front (the server compiles them). When any exist, suppress the
|
|
610
|
+
// "undeclared remote connector" notes entirely — we can't tell which
|
|
611
|
+
// remote connector corresponds to which local file.
|
|
612
|
+
const hasUnnamedLocalDefs = desiredConnectors.definitions.some((d) => d.key === null);
|
|
613
|
+
for (const def of desiredConnectors.definitions) {
|
|
614
|
+
rows.push(diffConnectorDefinition(def, installedKeys));
|
|
615
|
+
}
|
|
616
|
+
// Bundled connectors referenced by a desired auth-profile / connection that
|
|
617
|
+
// the org doesn't have installed yet — `lobu apply` will install them from
|
|
618
|
+
// the catalog's server-side `source_uri`. Surface them as plan rows so the
|
|
619
|
+
// operator approves these connector-definition mutations too. (Skipped when
|
|
620
|
+
// a locally-supplied connector declares the same key — that one wins.)
|
|
621
|
+
const installableByKey = new Map(remoteConnectorDefinitions
|
|
622
|
+
.filter((d) => d.installable && d.source_uri)
|
|
623
|
+
.map((d) => [d.key, d]));
|
|
624
|
+
for (const key of [...referencedConnectorKeys].sort()) {
|
|
625
|
+
if (installedKeys.has(key))
|
|
626
|
+
continue;
|
|
627
|
+
if (declaredKeys.has(key))
|
|
628
|
+
continue; // a local def supplies this key
|
|
629
|
+
const entry = installableByKey.get(key);
|
|
630
|
+
if (!entry?.source_uri)
|
|
631
|
+
continue;
|
|
632
|
+
rows.push({
|
|
633
|
+
kind: "connector-definition",
|
|
634
|
+
verb: "create",
|
|
635
|
+
id: key,
|
|
636
|
+
// No `desired` — this is a bundled install, handled by
|
|
637
|
+
// `installConnectorDefinitions`'s bundled-referenced loop, not the
|
|
638
|
+
// plan-row loop. The render falls back to `id` (the connector key).
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
// Conservative: never auto-uninstall remote connector definitions that
|
|
642
|
+
// aren't declared/referenced locally — just note them.
|
|
643
|
+
if (!hasUnnamedLocalDefs) {
|
|
644
|
+
for (const def of remoteConnectorDefinitions) {
|
|
645
|
+
if (def.installed &&
|
|
646
|
+
!declaredKeys.has(def.key) &&
|
|
647
|
+
!referencedConnectorKeys.has(def.key)) {
|
|
648
|
+
notes.push(`connector "${def.key}" is installed remotely but not declared in connectors/ — uninstall it manually if it's no longer wanted (lobu apply never auto-uninstalls connectors).`);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
const remoteAuthBySlug = new Map(remoteAuthProfiles.map((p) => [p.slug, p]));
|
|
653
|
+
const desiredAuthSlugs = new Set(desiredConnectors.authProfiles.map((p) => p.slug));
|
|
654
|
+
for (const profile of desiredConnectors.authProfiles) {
|
|
655
|
+
rows.push(diffAuthProfile(profile, remoteAuthBySlug.get(profile.slug)));
|
|
656
|
+
}
|
|
657
|
+
for (const remoteProfile of remoteAuthProfiles) {
|
|
658
|
+
if (!desiredAuthSlugs.has(remoteProfile.slug)) {
|
|
659
|
+
rows.push({
|
|
660
|
+
kind: "auth-profile",
|
|
661
|
+
verb: "drift",
|
|
662
|
+
id: remoteProfile.slug,
|
|
663
|
+
remote: remoteProfile,
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
const remoteConnBySlug = new Map(remoteConnections.map((c) => [c.slug, c]));
|
|
668
|
+
const desiredConnSlugs = new Set(desiredConnectors.connections.map((c) => c.slug));
|
|
669
|
+
for (const conn of desiredConnectors.connections) {
|
|
670
|
+
const remoteConn = remoteConnBySlug.get(conn.slug);
|
|
671
|
+
rows.push(diffConnection(conn, remoteConn));
|
|
672
|
+
// Nested feeds — diffed by feed_key within the connection. Only diffable
|
|
673
|
+
// when the connection already exists remotely; for a new connection the
|
|
674
|
+
// feeds are created right after the connection-creation step.
|
|
675
|
+
const remoteFeeds = remoteConn
|
|
676
|
+
? [...(remoteFeedsByConnectionId.get(remoteConn.id) ?? [])].sort((a, b) => a.feed_key.localeCompare(b.feed_key))
|
|
677
|
+
: [];
|
|
678
|
+
const remoteFeedByKey = new Map(remoteFeeds.map((f) => [f.feed_key, f]));
|
|
679
|
+
const desiredFeedKeys = new Set(conn.feeds.map((f) => f.feedKey));
|
|
680
|
+
for (const feed of conn.feeds) {
|
|
681
|
+
rows.push(diffFeed(conn.slug, feed, remoteFeedByKey.get(feed.feedKey)));
|
|
682
|
+
}
|
|
683
|
+
for (const remoteFeed of remoteFeeds) {
|
|
684
|
+
if (!desiredFeedKeys.has(remoteFeed.feed_key)) {
|
|
685
|
+
rows.push({
|
|
686
|
+
kind: "feed",
|
|
687
|
+
verb: "drift",
|
|
688
|
+
id: `${conn.slug}/${remoteFeed.feed_key}`,
|
|
689
|
+
connectionSlug: conn.slug,
|
|
690
|
+
remote: remoteFeed,
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
for (const remoteConn of remoteConnections) {
|
|
696
|
+
if (!desiredConnSlugs.has(remoteConn.slug)) {
|
|
697
|
+
rows.push({
|
|
698
|
+
kind: "connection",
|
|
699
|
+
verb: "drift",
|
|
700
|
+
id: remoteConn.slug,
|
|
701
|
+
remote: remoteConn,
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
}
|
|
317
705
|
}
|
|
318
706
|
const counts = { create: 0, update: 0, noop: 0, drift: 0 };
|
|
319
707
|
for (const row of rows)
|
|
320
708
|
counts[row.verb]++;
|
|
321
|
-
|
|
709
|
+
notes.sort();
|
|
710
|
+
return { rows, counts, notes };
|
|
322
711
|
}
|
|
323
712
|
//# sourceMappingURL=diff.js.map
|