aidevops 3.29.37 → 3.29.39
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/README.md +1 -1
- package/VERSION +1 -1
- package/aidevops.sh +1 -1
- package/package.json +1 -1
- package/packages/gui-api/src/app.ts +17 -1
- package/packages/gui-shared/src/contracts.ts +118 -1
- package/packages/gui-shared/src/fixtures.ts +122 -1
- package/packages/gui-shared/src/index.ts +1 -0
- package/packages/gui-shared/src/tambo.ts +260 -0
- package/packages/gui-web/src/AppNavigation.tsx +2 -0
- package/packages/gui-web/src/AppWorkspace.tsx +114 -1
- package/packages/gui-web/src/CommsConversationSurface.tsx +9 -1
- package/packages/gui-web/src/GenUiCards.tsx +52 -0
- package/packages/gui-web/src/app-model.ts +4 -3
- package/packages/gui-web/src/conversation-fixtures.ts +6 -1
- package/packages/gui-web/src/dashboard.ts +1 -1
- package/packages/gui-web/src/status-client.ts +9 -0
- package/packages/gui-web/src/styles.css +208 -0
- package/setup.sh +1 -1
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ The result: an AI operations platform that manages projects across every busines
|
|
|
58
58
|
[](https://github.com/marcusquinn)
|
|
59
59
|
|
|
60
60
|
<!-- Release & Version Info -->
|
|
61
|
-
[](https://github.com/marcusquinn/aidevops/releases)
|
|
62
62
|
[](https://www.npmjs.com/package/aidevops)
|
|
63
63
|
[](https://github.com/marcusquinn/homebrew-tap)
|
|
64
64
|
[](https://github.com/marcusquinn/aidevops)
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.29.
|
|
1
|
+
3.29.39
|
package/aidevops.sh
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import * as readline from "node:readline";
|
|
3
3
|
import { Hono } from "hono";
|
|
4
|
-
import { APP_ACTION_ROUTE_MANIFEST, APP_ACTION_STATUS_ROUTE_MANIFEST, BANNED_ROUTE_PATTERNS, createEnvelope, FILE_EXPLORER_ROUTE_MANIFEST, type GuiAppActionId, type GuiAppActionJobSummary, STATUS_ROUTE_MANIFEST, VAULT_STATUS_ROUTE_MANIFEST } from "../../gui-shared/src";
|
|
4
|
+
import { APP_ACTION_ROUTE_MANIFEST, APP_ACTION_STATUS_ROUTE_MANIFEST, BANNED_ROUTE_PATTERNS, createEnvelope, FILE_EXPLORER_ROUTE_MANIFEST, type GuiAppActionId, type GuiAppActionJobSummary, STATUS_ROUTE_MANIFEST, TAMBO_PROVIDER_CONFIG, TAMBO_PROXY_ROUTE_MANIFEST, VAULT_STATUS_ROUTE_MANIFEST } from "../../gui-shared/src";
|
|
5
5
|
import { readFileExplorer } from "./file-adapter";
|
|
6
6
|
import { readStatus, readVaultStatus } from "./status-adapter";
|
|
7
7
|
|
|
@@ -53,6 +53,22 @@ export function createGuiApiApp() {
|
|
|
53
53
|
return context.json(readVaultStatus());
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
+
app.get(TAMBO_PROXY_ROUTE_MANIFEST.route, (context) => {
|
|
57
|
+
const tenantRef = context.req.query("tenant_ref") ?? "local";
|
|
58
|
+
const workspaceRef = context.req.query("workspace_ref") ?? "aidevops";
|
|
59
|
+
const sessionRef = context.req.query("session_ref") ?? "conversation:local";
|
|
60
|
+
|
|
61
|
+
return context.json(createEnvelope({
|
|
62
|
+
operation_id: TAMBO_PROXY_ROUTE_MANIFEST.operation_id,
|
|
63
|
+
source: { surface: "conversations", authority: "server-side Tambo proxy metadata", path_refs: ["packages/gui-shared/src/tambo.ts"] },
|
|
64
|
+
data: {
|
|
65
|
+
...TAMBO_PROVIDER_CONFIG,
|
|
66
|
+
thread_key_ref: `${tenantRef}:${workspaceRef}:${sessionRef}`,
|
|
67
|
+
},
|
|
68
|
+
warnings: ["Tambo provider secrets stay server-side; this route exposes component metadata and scoped thread keys only."],
|
|
69
|
+
}));
|
|
70
|
+
});
|
|
71
|
+
|
|
56
72
|
app.post(APP_ACTION_ROUTE_MANIFEST.route, (context) => {
|
|
57
73
|
const appId = context.req.param("appId");
|
|
58
74
|
const action = context.req.param("action") as GuiAppActionId;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type GuiRouteClassification = "read" | "write" | "destructive";
|
|
2
2
|
|
|
3
|
-
export type GuiOperationId = "setup.status.read" | "capabilities.read" | "filesystem.read" | "vault.status.read" | "apps.action.run" | "apps.action.status";
|
|
3
|
+
export type GuiOperationId = "setup.status.read" | "capabilities.read" | "filesystem.read" | "vault.status.read" | "apps.action.run" | "apps.action.status" | "tambo.session.read";
|
|
4
4
|
|
|
5
5
|
export type GuiFileRootId = "agents" | "config" | "localSetup" | "git";
|
|
6
6
|
|
|
@@ -231,6 +231,122 @@ export interface GuiConversationUsageMetadata {
|
|
|
231
231
|
cost_ref: string | null;
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
+
export type GuiPulseWorkerEventType = "pulse_run" | "worker_session" | "command_job" | "ci_check" | "issue" | "pull_request" | "review" | "systemic_fix";
|
|
235
|
+
|
|
236
|
+
export type GuiPulseWorkerStatus = "healthy" | "attention" | "blocked" | "running" | "completed" | "failed" | "deferred";
|
|
237
|
+
|
|
238
|
+
export type GuiPulseWorkerOutcome = "merged" | "closed" | "in_progress" | "needs_maintainer_review" | "blocked" | "failed" | "deferred" | "created_followup";
|
|
239
|
+
|
|
240
|
+
export type GuiPulseWorkerSeverity = "info" | "success" | "warning" | "critical";
|
|
241
|
+
|
|
242
|
+
export type GuiPulseIssueOrigin = "aidevops_created" | "maintainer_created" | "origin_interactive" | "third_party" | "unknown";
|
|
243
|
+
|
|
244
|
+
export type GuiPulseAuthorAssociation = "OWNER" | "MEMBER" | "COLLABORATOR" | "CONTRIBUTOR" | "FIRST_TIME_CONTRIBUTOR" | "NONE" | "UNKNOWN";
|
|
245
|
+
|
|
246
|
+
export type GuiPulseResourceKind = "provider_model" | "github_api" | "ci_capacity" | "queue" | "cpu" | "memory" | "disk" | "network";
|
|
247
|
+
|
|
248
|
+
export type GuiPulseProviderId = GuiAiProviderId | "local" | "unknown";
|
|
249
|
+
|
|
250
|
+
export type GuiPulsePeriodBucket = "day" | "week" | "month" | "year";
|
|
251
|
+
|
|
252
|
+
export interface GuiPulseWorkerKpiCard {
|
|
253
|
+
id: string;
|
|
254
|
+
label: string;
|
|
255
|
+
value: string;
|
|
256
|
+
period_label: string;
|
|
257
|
+
scope_label: string;
|
|
258
|
+
comparison_label: string;
|
|
259
|
+
sample_size?: number;
|
|
260
|
+
status: GuiPulseWorkerStatus;
|
|
261
|
+
detail: string;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export interface GuiPulseWorkerUsageSnapshot extends GuiConversationUsageMetadata {
|
|
265
|
+
provider: GuiPulseProviderId;
|
|
266
|
+
model_ref: string | null;
|
|
267
|
+
cached_tokens: number;
|
|
268
|
+
wall_time_ms: number;
|
|
269
|
+
estimated_cost_ref: string | null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export interface GuiPulseResourceSnapshot {
|
|
273
|
+
kind: GuiPulseResourceKind;
|
|
274
|
+
label: string;
|
|
275
|
+
available_label: string;
|
|
276
|
+
pressure: "low" | "medium" | "high" | "unknown";
|
|
277
|
+
observed_at: string;
|
|
278
|
+
reset_at: string | null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export interface GuiPulseWorkerChartPoint {
|
|
282
|
+
period: GuiPulsePeriodBucket;
|
|
283
|
+
period_label: string;
|
|
284
|
+
scope_label: string;
|
|
285
|
+
bucket_start: string;
|
|
286
|
+
bucket_end: string;
|
|
287
|
+
value: number;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export interface GuiPulseWorkerChartSeries {
|
|
291
|
+
id: string;
|
|
292
|
+
label: string;
|
|
293
|
+
unit: "percent" | "count" | "tokens" | "cost_ref" | "milliseconds";
|
|
294
|
+
points: GuiPulseWorkerChartPoint[];
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export interface GuiPulseWorkerActivityEvent {
|
|
298
|
+
id: string;
|
|
299
|
+
type: GuiPulseWorkerEventType;
|
|
300
|
+
status: GuiPulseWorkerStatus;
|
|
301
|
+
outcome: GuiPulseWorkerOutcome;
|
|
302
|
+
severity: GuiPulseWorkerSeverity;
|
|
303
|
+
occurred_at: string;
|
|
304
|
+
title: string;
|
|
305
|
+
summary: string;
|
|
306
|
+
pulse_run_ref: string | null;
|
|
307
|
+
worker_session_ref: string | null;
|
|
308
|
+
issue_ref: string | null;
|
|
309
|
+
pull_request_ref: string | null;
|
|
310
|
+
command_job_ref: string | null;
|
|
311
|
+
repo_ref: string | null;
|
|
312
|
+
actor_ref: string | null;
|
|
313
|
+
issue_origin: GuiPulseIssueOrigin;
|
|
314
|
+
author_association: GuiPulseAuthorAssociation;
|
|
315
|
+
duration_ms: number | null;
|
|
316
|
+
usage: GuiPulseWorkerUsageSnapshot | null;
|
|
317
|
+
resources: GuiPulseResourceSnapshot[];
|
|
318
|
+
drilldown_sections: Array<{ label: string; body: string }>;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export interface GuiPulseWorkerFilterOption {
|
|
322
|
+
id: string;
|
|
323
|
+
label: string;
|
|
324
|
+
count: number;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export interface GuiPulseWorkerSummary {
|
|
328
|
+
value_policy: "metadata_only_no_prompt_payloads_no_secrets";
|
|
329
|
+
selected_period: GuiPulsePeriodBucket;
|
|
330
|
+
period_label: string;
|
|
331
|
+
scope_label: string;
|
|
332
|
+
comparison_label: string;
|
|
333
|
+
updated_at: string;
|
|
334
|
+
kpis: GuiPulseWorkerKpiCard[];
|
|
335
|
+
attention: Array<{ id: string; severity: GuiPulseWorkerSeverity; title: string; detail: string; event_ref: string | null }>;
|
|
336
|
+
filters: {
|
|
337
|
+
repos: GuiPulseWorkerFilterOption[];
|
|
338
|
+
event_types: GuiPulseWorkerFilterOption[];
|
|
339
|
+
outcomes: GuiPulseWorkerFilterOption[];
|
|
340
|
+
resources: GuiPulseWorkerFilterOption[];
|
|
341
|
+
providers: GuiPulseWorkerFilterOption[];
|
|
342
|
+
issue_origins: GuiPulseWorkerFilterOption[];
|
|
343
|
+
authors: GuiPulseWorkerFilterOption[];
|
|
344
|
+
author_associations: GuiPulseWorkerFilterOption[];
|
|
345
|
+
};
|
|
346
|
+
charts: GuiPulseWorkerChartSeries[];
|
|
347
|
+
events: GuiPulseWorkerActivityEvent[];
|
|
348
|
+
}
|
|
349
|
+
|
|
234
350
|
export interface GuiConversationMessage {
|
|
235
351
|
id: string;
|
|
236
352
|
conversation_id: string;
|
|
@@ -478,6 +594,7 @@ export interface GuiStatusData {
|
|
|
478
594
|
managed_apps: GuiManagedAppSummary[];
|
|
479
595
|
notifications: GuiNotificationSummary[];
|
|
480
596
|
vault: GuiVaultStatusData;
|
|
597
|
+
pulse_workers: GuiPulseWorkerSummary;
|
|
481
598
|
capabilities: GuiCapabilitySummary[];
|
|
482
599
|
secrets: GuiSecretReference[];
|
|
483
600
|
placeholders: string[];
|
|
@@ -1,4 +1,124 @@
|
|
|
1
|
-
import { GUI_FILE_ROOTS, type GuiFileExplorerData, type GuiStatusData } from "./contracts";
|
|
1
|
+
import { GUI_FILE_ROOTS, type GuiFileExplorerData, type GuiPulseWorkerSummary, type GuiStatusData } from "./contracts";
|
|
2
|
+
|
|
3
|
+
const pulseScope = "all managed repos";
|
|
4
|
+
const pulsePeriod = "Last 24h";
|
|
5
|
+
const pulseChartBuckets = {
|
|
6
|
+
day: { period_label: pulsePeriod, bucket_start: "2026-06-30T00:00:00.000Z", bucket_end: "2026-06-30T23:59:59.000Z", value: 86 },
|
|
7
|
+
week: { period_label: "This week", bucket_start: "2026-06-24T00:00:00.000Z", bucket_end: "2026-06-30T23:59:59.000Z", value: 82 },
|
|
8
|
+
month: { period_label: "This month", bucket_start: "2026-06-01T00:00:00.000Z", bucket_end: "2026-06-30T23:59:59.000Z", value: 82 },
|
|
9
|
+
year: { period_label: "This year", bucket_start: "2026-01-01T00:00:00.000Z", bucket_end: "2026-12-31T23:59:59.000Z", value: 82 },
|
|
10
|
+
} as const;
|
|
11
|
+
|
|
12
|
+
export const pulseWorkersFixture: GuiPulseWorkerSummary = {
|
|
13
|
+
value_policy: "metadata_only_no_prompt_payloads_no_secrets",
|
|
14
|
+
selected_period: "day",
|
|
15
|
+
period_label: pulsePeriod,
|
|
16
|
+
scope_label: pulseScope,
|
|
17
|
+
comparison_label: "Previous 24h baseline",
|
|
18
|
+
updated_at: "2026-06-30T09:45:00.000Z",
|
|
19
|
+
kpis: [
|
|
20
|
+
{ id: "healthy-sessions", label: "Healthy worker sessions", value: "86%", period_label: pulsePeriod, scope_label: pulseScope, comparison_label: "up 8 points vs previous 24h", sample_size: 21, status: "healthy", detail: "18 of 21 sessions ended merged, closed, or intentionally deferred." },
|
|
21
|
+
{ id: "attention-queue", label: "Attention queue", value: "5", period_label: pulsePeriod, scope_label: pulseScope, comparison_label: "down 2 vs previous 24h", sample_size: 184, status: "attention", detail: "Review stalls, API pressure, and CI capacity are grouped before raw events." },
|
|
22
|
+
{ id: "token-cost", label: "Token and cost sample", value: "1.8M / $14", period_label: pulsePeriod, scope_label: "provider scope", comparison_label: "estimated, metadata-only", sample_size: 11, status: "healthy", detail: "Provider/model totals exclude prompts, terminal payloads, secrets, and credential paths." },
|
|
23
|
+
{ id: "systemic-fixes", label: "Systemic fixes created", value: "9", period_label: "Trailing 7d", scope_label: "aidevops repo", comparison_label: "up 3 vs prior week", sample_size: 9, status: "completed", detail: "Blindspots become worker-ready follow-up tasks when patterns repeat." },
|
|
24
|
+
],
|
|
25
|
+
attention: [
|
|
26
|
+
{ id: "terminal-ci", severity: "warning", title: "2 PRs waiting on terminal CI results", detail: "Pending checks are tracked separately from failures to avoid noisy repair loops.", event_ref: "event:review-gate-25909" },
|
|
27
|
+
{ id: "stale-loop", severity: "warning", title: "1 worker loop matched stale-symptom diagnostics", detail: "Pulse should suggest a systemic diagnostics task instead of another retry.", event_ref: null },
|
|
28
|
+
{ id: "github-api", severity: "critical", title: "GitHub API allowance below dispatch comfort threshold", detail: "REST budget remains available; GraphQL-heavy list operations should pause.", event_ref: "event:community-25876" },
|
|
29
|
+
],
|
|
30
|
+
filters: {
|
|
31
|
+
repos: [{ id: "repo:marcusquinn/aidevops", label: "marcusquinn/aidevops", count: 184 }],
|
|
32
|
+
event_types: [{ id: "worker_session", label: "Worker session", count: 21 }, { id: "review", label: "Review gate", count: 17 }, { id: "issue", label: "Issue", count: 12 }],
|
|
33
|
+
outcomes: [{ id: "merged", label: "Merged", count: 12 }, { id: "in_progress", label: "In progress", count: 4 }, { id: "needs_maintainer_review", label: "Needs maintainer review", count: 2 }],
|
|
34
|
+
resources: [{ id: "provider_model", label: "Provider / model", count: 21 }, { id: "github_api", label: "GitHub API", count: 8 }, { id: "ci_capacity", label: "CI capacity", count: 7 }],
|
|
35
|
+
providers: [{ id: "openai:gpt-5.5", label: "OpenAI · gpt-5.5", count: 11 }, { id: "anthropic:sonnet", label: "Anthropic · sonnet", count: 7 }],
|
|
36
|
+
issue_origins: [{ id: "origin_interactive", label: "Interactive origin", count: 8 }, { id: "aidevops_created", label: "aidevops-created", count: 9 }, { id: "third_party", label: "Third-party/community", count: 3 }],
|
|
37
|
+
authors: [{ id: "actor:maintainer", label: "Maintainer", count: 17 }, { id: "actor:community", label: "Community reporter", count: 3 }],
|
|
38
|
+
author_associations: [{ id: "OWNER", label: "OWNER", count: 9 }, { id: "MEMBER", label: "MEMBER", count: 7 }, { id: "CONTRIBUTOR", label: "CONTRIBUTOR", count: 3 }],
|
|
39
|
+
},
|
|
40
|
+
charts: (["day", "week", "month", "year"] as const).map((period) => {
|
|
41
|
+
const bucket = pulseChartBuckets[period];
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
id: `health-${period}`,
|
|
45
|
+
label: `Health trend · ${period}`,
|
|
46
|
+
unit: "percent",
|
|
47
|
+
points: [{ period, period_label: bucket.period_label, scope_label: pulseScope, bucket_start: bucket.bucket_start, bucket_end: bucket.bucket_end, value: bucket.value }],
|
|
48
|
+
};
|
|
49
|
+
}),
|
|
50
|
+
events: [
|
|
51
|
+
{
|
|
52
|
+
id: "event:worker-25912",
|
|
53
|
+
type: "worker_session",
|
|
54
|
+
status: "running",
|
|
55
|
+
outcome: "in_progress",
|
|
56
|
+
severity: "info",
|
|
57
|
+
occurred_at: "2026-06-30T09:42:00.000Z",
|
|
58
|
+
title: "Worker session",
|
|
59
|
+
summary: "Implementing a GUI observability child task with read-only metadata fixtures.",
|
|
60
|
+
pulse_run_ref: "pulse:2026-06-30-am",
|
|
61
|
+
worker_session_ref: "worker:25912:gpt-5.5",
|
|
62
|
+
issue_ref: "#25912",
|
|
63
|
+
pull_request_ref: null,
|
|
64
|
+
command_job_ref: null,
|
|
65
|
+
repo_ref: "marcusquinn/aidevops",
|
|
66
|
+
actor_ref: "worker:auto-dispatch",
|
|
67
|
+
issue_origin: "origin_interactive",
|
|
68
|
+
author_association: "MEMBER",
|
|
69
|
+
duration_ms: 1420000,
|
|
70
|
+
usage: { provider: "openai", provider_ref: "provider:openai", model_ref: "model:gpt-5.5", input_tokens: 96000, output_tokens: 18000, cached_tokens: 42000, total_tokens: 114000, cost_ref: "cost:estimated-usd-0.88", estimated_cost_ref: "$0.88 estimated", wall_time_ms: 1420000 },
|
|
71
|
+
resources: [{ kind: "github_api", label: "REST API", available_label: "3,920 remaining", pressure: "low", observed_at: "2026-06-30T09:42:00.000Z", reset_at: "2026-06-30T10:00:00.000Z" }],
|
|
72
|
+
drilldown_sections: [{ label: "Why", body: "Parent task requested high-density Pulse & Workers observability without raw prompt payloads." }, { label: "Systemic fix", body: "Create follow-up only when repeated blindspots appear across workers." }],
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "event:review-gate-25909",
|
|
76
|
+
type: "review",
|
|
77
|
+
status: "completed",
|
|
78
|
+
outcome: "merged",
|
|
79
|
+
severity: "success",
|
|
80
|
+
occurred_at: "2026-06-30T09:18:00.000Z",
|
|
81
|
+
title: "Review gate",
|
|
82
|
+
summary: "PR merged after terminal checks completed successfully.",
|
|
83
|
+
pulse_run_ref: "pulse:2026-06-30-am",
|
|
84
|
+
worker_session_ref: "worker:25909:sonnet",
|
|
85
|
+
issue_ref: "#25909",
|
|
86
|
+
pull_request_ref: "#25909",
|
|
87
|
+
command_job_ref: "job:review-bot-gate",
|
|
88
|
+
repo_ref: "marcusquinn/aidevops",
|
|
89
|
+
actor_ref: "review-bot-gate",
|
|
90
|
+
issue_origin: "aidevops_created",
|
|
91
|
+
author_association: "OWNER",
|
|
92
|
+
duration_ms: 820000,
|
|
93
|
+
usage: { provider: "anthropic", provider_ref: "provider:anthropic", model_ref: "model:claude-sonnet", input_tokens: 32000, output_tokens: 10000, cached_tokens: 0, total_tokens: 42000, cost_ref: "cost:estimated-usd-0.31", estimated_cost_ref: "$0.31 estimated", wall_time_ms: 820000 },
|
|
94
|
+
resources: [{ kind: "ci_capacity", label: "CI", available_label: "green", pressure: "low", observed_at: "2026-06-30T09:18:00.000Z", reset_at: null }],
|
|
95
|
+
drilldown_sections: [{ label: "Evidence", body: "Terminal checks were green before merge; pending checks were not treated as failures." }],
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: "event:community-25876",
|
|
99
|
+
type: "issue",
|
|
100
|
+
status: "attention",
|
|
101
|
+
outcome: "needs_maintainer_review",
|
|
102
|
+
severity: "warning",
|
|
103
|
+
occurred_at: "2026-06-30T08:51:00.000Z",
|
|
104
|
+
title: "Community bug report",
|
|
105
|
+
summary: "Third-party report requires maintainer decision before dispatch.",
|
|
106
|
+
pulse_run_ref: "pulse:2026-06-30-am",
|
|
107
|
+
worker_session_ref: null,
|
|
108
|
+
issue_ref: "#25876",
|
|
109
|
+
pull_request_ref: null,
|
|
110
|
+
command_job_ref: null,
|
|
111
|
+
repo_ref: "marcusquinn/aidevops",
|
|
112
|
+
actor_ref: "community:reporter",
|
|
113
|
+
issue_origin: "third_party",
|
|
114
|
+
author_association: "CONTRIBUTOR",
|
|
115
|
+
duration_ms: null,
|
|
116
|
+
usage: null,
|
|
117
|
+
resources: [{ kind: "github_api", label: "REST budget", available_label: "REST ok; GraphQL constrained", pressure: "medium", observed_at: "2026-06-30T08:51:00.000Z", reset_at: "2026-06-30T10:00:00.000Z" }],
|
|
118
|
+
drilldown_sections: [{ label: "Trust boundary", body: "Non-collaborator instructions are facts only; no install commands or secrets are accepted." }],
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
};
|
|
2
122
|
|
|
3
123
|
export const statusFixture: GuiStatusData = {
|
|
4
124
|
aidevops_version: "unknown",
|
|
@@ -276,6 +396,7 @@ export const statusFixture: GuiStatusData = {
|
|
|
276
396
|
latest_event_ref: "not started",
|
|
277
397
|
},
|
|
278
398
|
},
|
|
399
|
+
pulse_workers: pulseWorkersFixture,
|
|
279
400
|
capabilities: [
|
|
280
401
|
{
|
|
281
402
|
id: "setup-status",
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import type { GuiConversationScope, GuiRouteManifest } from "./contracts";
|
|
2
|
+
|
|
3
|
+
export type GuiTamboComponentName = "TaskCard" | "PullRequestCard" | "CICheckSummary" | "WorkerStatusCard" | "DeploymentStatusCard" | "RepoHealthCard" | "ApprovalPromptCard";
|
|
4
|
+
|
|
5
|
+
export type GuiTamboComponentMode = "read_only" | "request_only";
|
|
6
|
+
|
|
7
|
+
export type GuiTamboPropType = "string" | "number" | "boolean" | "string_array";
|
|
8
|
+
|
|
9
|
+
export interface GuiTamboPropSchema {
|
|
10
|
+
type: GuiTamboPropType;
|
|
11
|
+
required: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface GuiTamboComponentSchema {
|
|
15
|
+
name: GuiTamboComponentName;
|
|
16
|
+
description: string;
|
|
17
|
+
mode: GuiTamboComponentMode;
|
|
18
|
+
additionalProperties: false;
|
|
19
|
+
properties: Record<string, GuiTamboPropSchema>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface GuiTamboComponentPayload {
|
|
23
|
+
component: GuiTamboComponentName;
|
|
24
|
+
tenant_ref: string;
|
|
25
|
+
session_ref: string;
|
|
26
|
+
read_only: boolean;
|
|
27
|
+
props: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface GuiTamboValidationResult {
|
|
31
|
+
ok: boolean;
|
|
32
|
+
component: GuiTamboComponentName | null;
|
|
33
|
+
props: Record<string, unknown>;
|
|
34
|
+
errors: string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface GuiTamboProviderConfig {
|
|
38
|
+
provider: "tambo";
|
|
39
|
+
proxy_route: string;
|
|
40
|
+
secret_policy: "server_only_no_browser_tokens";
|
|
41
|
+
thread_key_policy: "tenant_workspace_session_scoped";
|
|
42
|
+
components: GuiTamboComponentSchema[];
|
|
43
|
+
deferred_interactables: string[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const TAMBO_PROXY_ROUTE_MANIFEST: GuiRouteManifest = {
|
|
47
|
+
route: "/api/tambo/session",
|
|
48
|
+
method: "GET",
|
|
49
|
+
operation_id: "tambo.session.read",
|
|
50
|
+
classification: "read",
|
|
51
|
+
source_surface: "conversations",
|
|
52
|
+
adapter: "tambo.readProviderConfig",
|
|
53
|
+
command_pattern: ["server-proxied", "metadata-only", "no-browser-secrets"],
|
|
54
|
+
redactions: ["secret_values", "credential_paths", "private_key_material", "tambo_api_keys"],
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const TAMBO_COMPONENT_SCHEMAS: readonly GuiTamboComponentSchema[] = [
|
|
58
|
+
{
|
|
59
|
+
name: "TaskCard",
|
|
60
|
+
description: "Read-only task or issue status card.",
|
|
61
|
+
mode: "read_only",
|
|
62
|
+
additionalProperties: false,
|
|
63
|
+
properties: {
|
|
64
|
+
title: { type: "string", required: true },
|
|
65
|
+
status: { type: "string", required: true },
|
|
66
|
+
priority: { type: "string", required: false },
|
|
67
|
+
owner: { type: "string", required: false },
|
|
68
|
+
repo: { type: "string", required: false },
|
|
69
|
+
reference: { type: "string", required: false },
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "PullRequestCard",
|
|
74
|
+
description: "Read-only pull request summary card.",
|
|
75
|
+
mode: "read_only",
|
|
76
|
+
additionalProperties: false,
|
|
77
|
+
properties: {
|
|
78
|
+
title: { type: "string", required: true },
|
|
79
|
+
status: { type: "string", required: true },
|
|
80
|
+
branch: { type: "string", required: false },
|
|
81
|
+
checks: { type: "string", required: false },
|
|
82
|
+
review: { type: "string", required: false },
|
|
83
|
+
reference: { type: "string", required: false },
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "CICheckSummary",
|
|
88
|
+
description: "Read-only CI status summary card.",
|
|
89
|
+
mode: "read_only",
|
|
90
|
+
additionalProperties: false,
|
|
91
|
+
properties: {
|
|
92
|
+
status: { type: "string", required: true },
|
|
93
|
+
passed: { type: "number", required: true },
|
|
94
|
+
failed: { type: "number", required: true },
|
|
95
|
+
pending: { type: "number", required: false },
|
|
96
|
+
summary: { type: "string", required: false },
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: "WorkerStatusCard",
|
|
101
|
+
description: "Read-only worker progress card.",
|
|
102
|
+
mode: "read_only",
|
|
103
|
+
additionalProperties: false,
|
|
104
|
+
properties: {
|
|
105
|
+
worker: { type: "string", required: true },
|
|
106
|
+
status: { type: "string", required: true },
|
|
107
|
+
task: { type: "string", required: false },
|
|
108
|
+
progress: { type: "number", required: false },
|
|
109
|
+
blockers: { type: "string_array", required: false },
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: "DeploymentStatusCard",
|
|
114
|
+
description: "Read-only deployment environment status card.",
|
|
115
|
+
mode: "read_only",
|
|
116
|
+
additionalProperties: false,
|
|
117
|
+
properties: {
|
|
118
|
+
environment: { type: "string", required: true },
|
|
119
|
+
status: { type: "string", required: true },
|
|
120
|
+
version: { type: "string", required: false },
|
|
121
|
+
health: { type: "string", required: false },
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: "RepoHealthCard",
|
|
126
|
+
description: "Read-only repository health card.",
|
|
127
|
+
mode: "read_only",
|
|
128
|
+
additionalProperties: false,
|
|
129
|
+
properties: {
|
|
130
|
+
repo: { type: "string", required: true },
|
|
131
|
+
status: { type: "string", required: true },
|
|
132
|
+
open_prs: { type: "number", required: false },
|
|
133
|
+
failing_checks: { type: "number", required: false },
|
|
134
|
+
notes: { type: "string_array", required: false },
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: "ApprovalPromptCard",
|
|
139
|
+
description: "Request-only approval card. Mutating approval execution is deferred until audited approval tooling exists.",
|
|
140
|
+
mode: "request_only",
|
|
141
|
+
additionalProperties: false,
|
|
142
|
+
properties: {
|
|
143
|
+
action: { type: "string", required: true },
|
|
144
|
+
reason: { type: "string", required: true },
|
|
145
|
+
risk: { type: "string", required: true },
|
|
146
|
+
requested_by: { type: "string", required: false },
|
|
147
|
+
disabled: { type: "boolean", required: true },
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
] as const;
|
|
151
|
+
|
|
152
|
+
export const TAMBO_PROVIDER_CONFIG: GuiTamboProviderConfig = {
|
|
153
|
+
provider: "tambo",
|
|
154
|
+
proxy_route: TAMBO_PROXY_ROUTE_MANIFEST.route,
|
|
155
|
+
secret_policy: "server_only_no_browser_tokens",
|
|
156
|
+
thread_key_policy: "tenant_workspace_session_scoped",
|
|
157
|
+
components: [...TAMBO_COMPONENT_SCHEMAS],
|
|
158
|
+
deferred_interactables: ["mutating approvals", "deployment actions", "PR merge/retry actions", "worker dispatch writes"],
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export function validateTamboComponentPayload(payload: unknown, scope: GuiConversationScope): GuiTamboValidationResult {
|
|
162
|
+
const envelope = validateTamboPayloadEnvelope(payload, scope);
|
|
163
|
+
if (!envelope.ok) {
|
|
164
|
+
return envelope.result;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const errors = validateTamboComponentProps(envelope.schema, envelope.props);
|
|
168
|
+
return { ok: errors.length === 0, component: envelope.schema.name, props: envelope.props, errors };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
type TamboPayloadEnvelopeValidation =
|
|
172
|
+
| { ok: true; schema: GuiTamboComponentSchema; props: Record<string, unknown> }
|
|
173
|
+
| { ok: false; result: GuiTamboValidationResult };
|
|
174
|
+
|
|
175
|
+
function validateTamboPayloadEnvelope(payload: unknown, scope: GuiConversationScope): TamboPayloadEnvelopeValidation {
|
|
176
|
+
if (!isRecord(payload)) {
|
|
177
|
+
return { ok: false, result: invalid(null, {}, "payload_not_object") };
|
|
178
|
+
}
|
|
179
|
+
if (typeof payload.component !== "string") {
|
|
180
|
+
return { ok: false, result: invalid(null, {}, "component_missing") };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const schema = findTamboComponentSchema(payload.component);
|
|
184
|
+
if (schema === undefined) {
|
|
185
|
+
return { ok: false, result: invalid(null, {}, "component_not_registered") };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const envelopeError = validateScopedTamboEnvelope(payload, scope);
|
|
189
|
+
if (envelopeError !== null) {
|
|
190
|
+
return { ok: false, result: invalid(schema.name, {}, envelopeError) };
|
|
191
|
+
}
|
|
192
|
+
if (!isRecord(payload.props)) {
|
|
193
|
+
return { ok: false, result: invalid(schema.name, {}, "props_not_object") };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return { ok: true, schema, props: payload.props };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function findTamboComponentSchema(component: string): GuiTamboComponentSchema | undefined {
|
|
200
|
+
return TAMBO_COMPONENT_SCHEMAS.find((candidate) => candidate.name === component);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function validateScopedTamboEnvelope(payload: Record<string, unknown>, scope: GuiConversationScope): string | null {
|
|
204
|
+
if (payload.tenant_ref !== scope.tenant_ref) {
|
|
205
|
+
return "tenant_scope_mismatch";
|
|
206
|
+
}
|
|
207
|
+
if (typeof payload.session_ref !== "string" || payload.session_ref.length === 0) {
|
|
208
|
+
return "session_ref_missing";
|
|
209
|
+
}
|
|
210
|
+
if (payload.read_only !== true) {
|
|
211
|
+
return "component_not_read_only";
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function validateTamboComponentProps(schema: GuiTamboComponentSchema, props: Record<string, unknown>): string[] {
|
|
217
|
+
return [
|
|
218
|
+
...findUnexpectedTamboProps(schema, props),
|
|
219
|
+
...findInvalidTamboProps(schema, props),
|
|
220
|
+
...findTamboComponentSpecificErrors(schema, props),
|
|
221
|
+
];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function findUnexpectedTamboProps(schema: GuiTamboComponentSchema, props: Record<string, unknown>): string[] {
|
|
225
|
+
return Object.keys(props)
|
|
226
|
+
.filter((key) => schema.properties[key] === undefined)
|
|
227
|
+
.map((key) => `unexpected_prop:${key}`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function findInvalidTamboProps(schema: GuiTamboComponentSchema, props: Record<string, unknown>): string[] {
|
|
231
|
+
return Object.entries(schema.properties).flatMap(([key, propSchema]) => {
|
|
232
|
+
const value = props[key];
|
|
233
|
+
if (value === undefined) {
|
|
234
|
+
return propSchema.required ? [`missing_prop:${key}`] : [];
|
|
235
|
+
}
|
|
236
|
+
return valueMatchesType(value, propSchema.type) ? [] : [`invalid_prop:${key}`];
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function findTamboComponentSpecificErrors(schema: GuiTamboComponentSchema, props: Record<string, unknown>): string[] {
|
|
241
|
+
if (schema.name === "ApprovalPromptCard" && props.disabled !== true) {
|
|
242
|
+
return ["approval_prompt_must_be_disabled"];
|
|
243
|
+
}
|
|
244
|
+
return [];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function invalid(component: GuiTamboComponentName | null, props: Record<string, unknown>, error: string): GuiTamboValidationResult {
|
|
248
|
+
return { ok: false, component, props, errors: [error] };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
252
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function valueMatchesType(value: unknown, type: GuiTamboPropType): boolean {
|
|
256
|
+
if (type === "string_array") {
|
|
257
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
258
|
+
}
|
|
259
|
+
return typeof value === type;
|
|
260
|
+
}
|
|
@@ -3,6 +3,7 @@ import type { GuiMachineSummary, GuiStatusData } from "@aidevops/gui-shared";
|
|
|
3
3
|
import { useEffect, useState, type ReactNode } from "react";
|
|
4
4
|
import type { IconType } from "react-icons";
|
|
5
5
|
import {
|
|
6
|
+
FiActivity,
|
|
6
7
|
FiBookmark,
|
|
7
8
|
FiBox,
|
|
8
9
|
FiBriefcase,
|
|
@@ -41,6 +42,7 @@ import { VaultPadlock, vaultCollectionForSurface } from "./VaultBadges";
|
|
|
41
42
|
export { hueFromInputValue } from "./AppearanceControls";
|
|
42
43
|
|
|
43
44
|
const surfaceIcons: Record<SurfaceIconName, IconType> = {
|
|
45
|
+
activity: FiActivity,
|
|
44
46
|
apps: FiBox,
|
|
45
47
|
bookmark: FiBookmark,
|
|
46
48
|
brand: FiBriefcase,
|
|
@@ -8,6 +8,7 @@ import { inventorySurfaceConfigs, surfaceIds, text } from "./app-model";
|
|
|
8
8
|
import { CommandPalette, type CommandPaletteSelection, commandPaletteShortcutQuery } from "./CommandPalette";
|
|
9
9
|
import { CommsConversationSurface } from "./CommsConversationSurface";
|
|
10
10
|
import { FileExplorerSurface } from "./FileExplorerSurface";
|
|
11
|
+
import { TamboConversationPart } from "./GenUiCards";
|
|
11
12
|
import { AppsSurface, EditableInventorySurface, InstallationSurface } from "./InventorySurfaces";
|
|
12
13
|
import { AiProvidersSurface, LocalReposSurface, LockedVaultGate, OverviewSurface, PlannedSurface, ProjectsSurface, SecuritySurface, VaultSurface } from "./StatusSurfaces";
|
|
13
14
|
import { isVaultSurfaceLocked, vaultCollectionForSurface } from "./VaultBadges";
|
|
@@ -289,6 +290,10 @@ function AiSessionsSurface({ selectedRepoIndex, selectedSessionId, status }: { s
|
|
|
289
290
|
<AttachmentCard label="Context attachment" detail={selectedRepo ? `Selected repo context: ${selectedRepo.path_ref}` : "No local repos were discovered yet."} />
|
|
290
291
|
<MessageMarker label="Reasoning" detail="Reasoning disclosure, tool status, sources, retries, errors, and token usage render here when the AI transport supplies those parts." />
|
|
291
292
|
<ToolStatusCard />
|
|
293
|
+
<TamboConversationPart
|
|
294
|
+
part={{ id: "ai-session-tambo-preview", message_id: "ai-session-preview", kind: "tambo_component", ordinal: 1, text: null, payload_json: { component: "RepoHealthCard", tenant_ref: "local", session_ref: selectedSession?.id_ref ?? "ai-session-preview", read_only: true, props: { repo: selectedRepo?.name ?? "local workspace", status: "ready for Tambo cards", open_prs: 0, failing_checks: 0, notes: ["Server-proxied provider metadata", "Read-only schemas first"] } }, file_ref: null, source_ref: "tambo:ai-session-preview" }}
|
|
295
|
+
scope={{ tenant_ref: "local", workspace_ref: "aidevops", repo_ref: selectedRepo?.slug ?? null }}
|
|
296
|
+
/>
|
|
292
297
|
</div>
|
|
293
298
|
<form className="chat-composer ai-composer" aria-label="AI prompt composer" data-tour="ai-composer">
|
|
294
299
|
<button disabled title="Attachments need the audited upload/context adapter" type="button"><FiPaperclip aria-hidden="true" /> Attach</button>
|
|
@@ -380,7 +385,7 @@ function SurfaceContent({ activeItem, activeSurface, fileRoot, openSurface, stat
|
|
|
380
385
|
aiSessions: <AiSessionsSurface selectedRepoIndex={0} status={status} />,
|
|
381
386
|
channels: <CommsConversationSurface mode="channels" />,
|
|
382
387
|
directMessages: <CommsConversationSurface mode="directMessages" />,
|
|
383
|
-
workers: <
|
|
388
|
+
workers: <PulseWorkersSurface status={status} />,
|
|
384
389
|
repos: <PlannedSurface label={text.repos} detail={text.reposIntro} />,
|
|
385
390
|
deployments: <PlannedSurface label={text.deployments} detail={text.deploymentsIntro} />,
|
|
386
391
|
routines: <PlannedSurface label={text.routines} detail={text.routineDetail} />,
|
|
@@ -432,6 +437,114 @@ function SurfaceContent({ activeItem, activeSurface, fileRoot, openSurface, stat
|
|
|
432
437
|
return staticSurfaces[activeSurface] ?? null;
|
|
433
438
|
}
|
|
434
439
|
|
|
440
|
+
const pulseFilters = ["Repo", "Event type", "Outcome", "Resource", "Provider / model", "Issue origin", "Author", "Author association"] as const;
|
|
441
|
+
|
|
442
|
+
function PulseWorkersSurface({ status }: { status: GuiStatusData }): ReactElement {
|
|
443
|
+
const pulse = status.pulse_workers;
|
|
444
|
+
|
|
445
|
+
return (
|
|
446
|
+
<section className="pulse-workers-surface" aria-label={text.workers}>
|
|
447
|
+
<div className="planned-card pulse-hero">
|
|
448
|
+
<p className="eyebrow">Observability shell · fixture-backed · read-only</p>
|
|
449
|
+
<h2>{text.workers}</h2>
|
|
450
|
+
<p>{text.workersIntro}</p>
|
|
451
|
+
<section className="pulse-scope-strip" aria-label="Pulse scope and time range">
|
|
452
|
+
<span><strong>Period</strong> {pulse.period_label} · {pulse.scope_label}</span>
|
|
453
|
+
<span><strong>Comparison</strong> {pulse.comparison_label}</span>
|
|
454
|
+
<span><strong>Sample</strong> {pulse.events.length} canonical events · {pulse.kpis.reduce((total, kpi) => total + (kpi.sample_size ?? 0), 0)} samples</span>
|
|
455
|
+
<span><strong>Trust boundary</strong> Metadata/status only; protected payloads excluded</span>
|
|
456
|
+
</section>
|
|
457
|
+
</div>
|
|
458
|
+
<section className="pulse-kpi-grid" aria-label="Pulse health summary">
|
|
459
|
+
{pulse.kpis.map((kpi) => (
|
|
460
|
+
<article className="metric-card pulse-kpi-card" key={kpi.id}>
|
|
461
|
+
<span>{kpi.label} · {kpi.period_label} · {kpi.scope_label}</span>
|
|
462
|
+
<strong>{kpi.value}</strong>
|
|
463
|
+
<p>{kpi.detail} {kpi.comparison_label}</p>
|
|
464
|
+
</article>
|
|
465
|
+
))}
|
|
466
|
+
</section>
|
|
467
|
+
<section className="pulse-layout" aria-label="Pulse observability hierarchy">
|
|
468
|
+
<article className="planned-card pulse-attention-panel">
|
|
469
|
+
<div className="split-heading">
|
|
470
|
+
<div>
|
|
471
|
+
<p className="eyebrow">Exceptions first</p>
|
|
472
|
+
<h3>Needs attention</h3>
|
|
473
|
+
</div>
|
|
474
|
+
<span className="count-pill">planned actions disabled</span>
|
|
475
|
+
</div>
|
|
476
|
+
<ul>
|
|
477
|
+
{pulse.attention.map((item) => <li key={item.id}>{item.title}: {item.detail}</li>)}
|
|
478
|
+
</ul>
|
|
479
|
+
<button disabled title="Action routes need audited worker control APIs" type="button">Create systemic fix (planned)</button>
|
|
480
|
+
</article>
|
|
481
|
+
<article className="planned-card pulse-chart-panel">
|
|
482
|
+
<p className="eyebrow">Trends</p>
|
|
483
|
+
<h3>Health trend placeholder</h3>
|
|
484
|
+
<div className="pulse-chart-placeholder" aria-label="Chart placeholder showing health, queue, token, cost, API, and CI capacity trends" role="img">
|
|
485
|
+
<span>health</span><span>queue</span><span>tokens</span><span>cost</span><span>api</span><span>ci</span>
|
|
486
|
+
</div>
|
|
487
|
+
<p>Charts derive from {pulse.charts.map((chart) => chart.points[0]?.period).filter(Boolean).join("/")} fixture buckets across the canonical event stream: Pulse, workers, commands, CI, issues, PRs, reviews, and outcomes.</p>
|
|
488
|
+
</article>
|
|
489
|
+
</section>
|
|
490
|
+
<section className="planned-card pulse-activity-panel" aria-label="Unified activity stream">
|
|
491
|
+
<div className="split-heading">
|
|
492
|
+
<div>
|
|
493
|
+
<p className="eyebrow">Canonical stream</p>
|
|
494
|
+
<h3>Unified activity</h3>
|
|
495
|
+
</div>
|
|
496
|
+
<section className="pulse-filter-row" aria-label="Quick filters">
|
|
497
|
+
{pulseFilters.map((filter) => <button disabled key={filter} title={`${filter} filter is planned`} type="button">{filter}</button>)}
|
|
498
|
+
</section>
|
|
499
|
+
</div>
|
|
500
|
+
<table className="pulse-activity-table" aria-label="Pulse and worker events">
|
|
501
|
+
<thead>
|
|
502
|
+
<tr className="pulse-activity-row pulse-activity-header">
|
|
503
|
+
<th scope="col">When</th><th scope="col">Event</th><th scope="col">Scope</th><th scope="col">Outcome</th><th scope="col">Resource</th><th scope="col">Origin / actor</th>
|
|
504
|
+
</tr>
|
|
505
|
+
</thead>
|
|
506
|
+
<tbody>
|
|
507
|
+
{pulse.events.map((row) => (
|
|
508
|
+
<tr className="pulse-activity-row" key={row.id}>
|
|
509
|
+
<td data-label="When">{row.occurred_at.slice(11, 16)}</td>
|
|
510
|
+
<td data-label="Event">{row.title}</td>
|
|
511
|
+
<td data-label="Scope">{[row.repo_ref, row.issue_ref ?? row.pull_request_ref].filter(Boolean).join(" ")}</td>
|
|
512
|
+
<td data-label="Outcome">{row.outcome.replaceAll("_", " ")}</td>
|
|
513
|
+
<td data-label="Resource">{resourceSummary(row)}</td>
|
|
514
|
+
<td data-label="Origin / actor">{row.issue_origin.replaceAll("_", "-")} · {row.author_association}</td>
|
|
515
|
+
</tr>
|
|
516
|
+
))}
|
|
517
|
+
</tbody>
|
|
518
|
+
</table>
|
|
519
|
+
<p className="notice compact-notice">Mobile activity cards replace the dense table on small screens. Detail drawer becomes a full-screen sheet on small screens, and terminal panel becomes full-screen later.</p>
|
|
520
|
+
</section>
|
|
521
|
+
<section className="pulse-layout" aria-label="Drilldown and planned actions">
|
|
522
|
+
<article className="planned-card pulse-drilldown-panel">
|
|
523
|
+
<p className="eyebrow">Drilldown placeholder</p>
|
|
524
|
+
<h3>What happened, when, why, how, who acted, and what resources were available</h3>
|
|
525
|
+
<p>Selected event details will connect timeline evidence, issue/PR/review context, provider/model/tokens/time/cost metadata, GitHub/API allowance, queue/concurrency, CI capacity, and local resource snapshots without exposing secrets.</p>
|
|
526
|
+
</article>
|
|
527
|
+
<article className="planned-card pulse-actions-panel">
|
|
528
|
+
<p className="eyebrow">Planned controls</p>
|
|
529
|
+
<h3>Actions stay disabled until audited routes land</h3>
|
|
530
|
+
<button disabled title="Terminal output needs a read-only command-output adapter" type="button">Open terminal output (planned)</button>
|
|
531
|
+
<button disabled title="Dispatch needs worker control and trust-boundary APIs" type="button">Redispatch worker (planned)</button>
|
|
532
|
+
<button disabled title="Persistence needs a write-action manifest and audit trail" type="button">Save systemic fix (planned)</button>
|
|
533
|
+
</article>
|
|
534
|
+
</section>
|
|
535
|
+
</section>
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function resourceSummary(row: GuiStatusData["pulse_workers"]["events"][number]): string {
|
|
540
|
+
const model = row.usage?.model_ref?.replace("model:", "");
|
|
541
|
+
const providerLabel = row.usage?.provider === "openai" ? "OpenAI" : row.usage?.provider === "anthropic" ? "Anthropic" : row.usage?.provider;
|
|
542
|
+
const provider = row.usage === null ? row.resources[0]?.available_label ?? "metadata only" : `${providerLabel ?? "Provider"} · ${model ?? "model metadata pending"}`;
|
|
543
|
+
const tokens = row.usage === null ? "" : ` · ${row.usage.total_tokens.toLocaleString()} tokens`;
|
|
544
|
+
|
|
545
|
+
return `${provider}${tokens}`;
|
|
546
|
+
}
|
|
547
|
+
|
|
435
548
|
function HelpSurface(): ReactElement {
|
|
436
549
|
const shortcuts = [
|
|
437
550
|
["#", "Channels"],
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { FiAtSign, FiHash, FiMessageSquare, FiSearch } from "react-icons/fi";
|
|
2
2
|
import { sortConversationMessageParts, sortConversationMessages, type GuiConversationMessage, type GuiConversationMessagePart, type GuiConversationThread } from "../../gui-shared/src";
|
|
3
|
+
import { TamboConversationPart } from "./GenUiCards";
|
|
3
4
|
import { text } from "./app-model";
|
|
4
5
|
import { conversationThreads } from "./conversation-fixtures";
|
|
5
6
|
|
|
@@ -91,13 +92,20 @@ function ConversationMessageRow({ message, parts, thread }: { message: GuiConver
|
|
|
91
92
|
return (
|
|
92
93
|
<article className={`chat-bubble ${speaker}`} data-sender-kind={message.sender_kind}>
|
|
93
94
|
<strong>{sender?.display_name ?? message.sender_kind}</strong>
|
|
94
|
-
{parts.map((part) => <
|
|
95
|
+
{parts.map((part) => <ConversationPart key={part.id} part={part} thread={thread} />)}
|
|
95
96
|
<small>{message.status} · {message.created_at}</small>
|
|
96
97
|
{reactions.length > 0 ? <div className="reaction-row">{reactions.map((reaction) => <span key={reaction.id}>{reaction.reaction}</span>)}</div> : null}
|
|
97
98
|
</article>
|
|
98
99
|
);
|
|
99
100
|
}
|
|
100
101
|
|
|
102
|
+
function ConversationPart({ part, thread }: { part: GuiConversationMessagePart; thread: GuiConversationThread }) {
|
|
103
|
+
if (part.kind === "tambo_component" || part.kind === "approval_prompt") {
|
|
104
|
+
return <TamboConversationPart part={part} scope={thread.conversation.scope} />;
|
|
105
|
+
}
|
|
106
|
+
return <p>{part.text ?? part.kind}</p>;
|
|
107
|
+
}
|
|
108
|
+
|
|
101
109
|
function participantSummary(thread: GuiConversationThread): string {
|
|
102
110
|
const active = conversationParticipants(thread).filter((participant) => participant.membership_state === "active").length;
|
|
103
111
|
return `${active} members`;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { validateTamboComponentPayload, type GuiConversationMessagePart, type GuiConversationScope, type GuiTamboValidationResult } from "../../gui-shared/src";
|
|
2
|
+
|
|
3
|
+
export function TamboConversationPart({ part, scope }: { part: GuiConversationMessagePart; scope: GuiConversationScope }) {
|
|
4
|
+
const validation = validateTamboComponentPayload(part.payload_json, scope);
|
|
5
|
+
|
|
6
|
+
if (!validation.ok) {
|
|
7
|
+
return <div className="genui-card genui-card-invalid" data-genui-component={validation.component ?? "unknown"} role="note"><strong>Unsupported DevOps card</strong><small>{validation.errors.join(", ")}</small></div>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return <DevOpsCard validation={validation} />;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function DevOpsCard({ validation }: { validation: GuiTamboValidationResult }) {
|
|
14
|
+
const props = validation.props;
|
|
15
|
+
const entries = Object.entries(props).filter(([key]) => key !== "title" && key !== "disabled");
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<section className="genui-card" data-genui-component={validation.component ?? "unknown"} aria-label={`${validation.component} DevOps card`}>
|
|
19
|
+
<header>
|
|
20
|
+
<p className="eyebrow">Tambo GenUI · read-only</p>
|
|
21
|
+
<h3>{stringProp(props.title) ?? validation.component}</h3>
|
|
22
|
+
</header>
|
|
23
|
+
<dl>
|
|
24
|
+
{entries.map(([key, value]) => <CardProperty key={key} name={key} value={value} />)}
|
|
25
|
+
</dl>
|
|
26
|
+
{validation.component === "ApprovalPromptCard" ? <p className="genui-deferred">Approval execution is deferred until audited approval tooling exists.</p> : null}
|
|
27
|
+
</section>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function CardProperty({ name, value }: { name: string; value: unknown }) {
|
|
32
|
+
return (
|
|
33
|
+
<div>
|
|
34
|
+
<dt>{name.replaceAll("_", " ")}</dt>
|
|
35
|
+
<dd>{formatValue(value)}</dd>
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function formatValue(value: unknown): string {
|
|
41
|
+
if (Array.isArray(value)) {
|
|
42
|
+
return value.join(", ");
|
|
43
|
+
}
|
|
44
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
45
|
+
return String(value);
|
|
46
|
+
}
|
|
47
|
+
return "—";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function stringProp(value: unknown): string | null {
|
|
51
|
+
return typeof value === "string" ? value : null;
|
|
52
|
+
}
|
|
@@ -19,6 +19,7 @@ export type FontPreference =
|
|
|
19
19
|
| "Ubuntu Mono";
|
|
20
20
|
export type FontSizePreference = "xs" | "s" | "m" | "lg" | "xl";
|
|
21
21
|
export const surfaceIconNames = {
|
|
22
|
+
activity: true,
|
|
22
23
|
apps: true,
|
|
23
24
|
bookmark: true,
|
|
24
25
|
brand: true,
|
|
@@ -273,8 +274,8 @@ export const text = {
|
|
|
273
274
|
socialMedia: "Social Media",
|
|
274
275
|
socialMediaIntro: "Social profiles, pages, handles, audience notes, and channel ownership are planned.",
|
|
275
276
|
tasks: "Tasks",
|
|
276
|
-
workers: "Workers",
|
|
277
|
-
workersIntro: "
|
|
277
|
+
workers: "Pulse & Workers",
|
|
278
|
+
workersIntro: "Pulse & Workers brings worker sessions, event streams, outcomes, resources, and systemic fixes into one observability console so attention goes to exceptions, bottlenecks, and reusable improvements instead of raw noise.",
|
|
278
279
|
theme: "Theme",
|
|
279
280
|
appearance: "Appearance",
|
|
280
281
|
hue: "Hue",
|
|
@@ -367,7 +368,7 @@ export const navGroups: SurfaceNavGroup[] = [
|
|
|
367
368
|
mode: "devops",
|
|
368
369
|
items: [
|
|
369
370
|
plannedNavItem("aiSessions", text.aiSessions, "AI chat and OpenCode sessions", "terminal"),
|
|
370
|
-
plannedNavItem("workers", text.workers, "
|
|
371
|
+
plannedNavItem("workers", text.workers, "Pulse, worker sessions, outcomes, and resources", "activity"),
|
|
371
372
|
{ id: "git", label: text.localRepos, description: "~/Git explorer", icon: "folder" },
|
|
372
373
|
plannedNavItem("repos", text.repos, "Unified local and remote repo context", "git"),
|
|
373
374
|
{ id: "projects", label: text.projects, description: "repos.json summary", icon: "git" },
|
|
@@ -11,10 +11,12 @@ export const conversationThreads: GuiConversationThread[] = [
|
|
|
11
11
|
messages: [
|
|
12
12
|
{ id: "message-general-1", conversation_id: "channel-general", sender_participant_id: "participant-system", sender_kind: "system", sequence: 1, status: "sent", usage: null, created_at: "2026-06-27T12:00:00Z", edited_at: null },
|
|
13
13
|
{ id: "message-general-2", conversation_id: "channel-general", sender_participant_id: "participant-ai", sender_kind: "ai_assistant", sequence: 2, status: "delivered", usage: { provider_ref: "local", model_ref: "workflow-summary", input_tokens: 0, output_tokens: 0, total_tokens: 0, cost_ref: null }, created_at: "2026-06-27T12:15:00Z", edited_at: null },
|
|
14
|
+
{ id: "message-general-3", conversation_id: "channel-general", sender_participant_id: "participant-ai", sender_kind: "ai_assistant", sequence: 3, status: "delivered", usage: { provider_ref: "tambo", model_ref: "devops-card", input_tokens: 0, output_tokens: 0, total_tokens: 0, cost_ref: null }, created_at: "2026-06-27T12:18:00Z", edited_at: null },
|
|
14
15
|
],
|
|
15
16
|
parts: [
|
|
16
17
|
{ id: "part-general-1", message_id: "message-general-1", kind: "event_marker", ordinal: 1, text: "#general is ready for repo, deployment, review, and incident coordination.", payload_json: null, file_ref: null, source_ref: "seed" },
|
|
17
18
|
{ id: "part-general-2", message_id: "message-general-2", kind: "text", ordinal: 1, text: "Mention @AI DevOps or a worker to turn a thread into an audited task once write routes land.", payload_json: null, file_ref: null, source_ref: null },
|
|
19
|
+
{ id: "part-general-3", message_id: "message-general-3", kind: "tambo_component", ordinal: 1, text: null, payload_json: { component: "TaskCard", tenant_ref: "local", session_ref: "channel-general", read_only: true, props: { title: "Integrate Tambo GenUI cards", status: "read-only preview", priority: "high", owner: "AI DevOps", repo: "aidevops", reference: "#25713" } }, file_ref: null, source_ref: "tambo:seed" },
|
|
18
20
|
],
|
|
19
21
|
reactions: [{ id: "reaction-general-1", message_id: "message-general-2", participant_id: "participant-local", reaction: "ack", created_at: "2026-06-27T12:16:00Z" }],
|
|
20
22
|
read_states: [{ conversation_id: "channel-general", participant_id: "participant-local", last_read_message_id: "message-general-1", last_read_sequence: 1, updated_at: "2026-06-27T12:10:00Z" }],
|
|
@@ -31,7 +33,10 @@ export const conversationThreads: GuiConversationThread[] = [
|
|
|
31
33
|
conversation: { id: "dm-ai-devops", type: "dm", title: "AI DevOps", scope: { tenant_ref: "local", workspace_ref: "aidevops", repo_ref: null }, source_ref: "seed:dms/ai-devops", status: "read_only", created_at: "2026-06-27T00:00:00Z", updated_at: "2026-06-27T12:40:00Z" },
|
|
32
34
|
participants: [{ id: "participant-dm-local", conversation_id: "dm-ai-devops", kind: "human", display_name: "Local user", identity_ref: "local:user", agent_ref: null, worker_ref: null, membership_state: "active", joined_at: "2026-06-27T00:00:00Z" }, { id: "participant-dm-ai", conversation_id: "dm-ai-devops", kind: "ai_assistant", display_name: "AI DevOps", identity_ref: null, agent_ref: "aidevops", worker_ref: null, membership_state: "active", joined_at: "2026-06-27T00:00:00Z" }],
|
|
33
35
|
messages: [{ id: "message-dm-1", conversation_id: "dm-ai-devops", sender_participant_id: "participant-dm-ai", sender_kind: "ai_assistant", sequence: 1, status: "delivered", usage: null, created_at: "2026-06-27T12:40:00Z", edited_at: null }],
|
|
34
|
-
parts: [
|
|
36
|
+
parts: [
|
|
37
|
+
{ id: "part-dm-1", message_id: "message-dm-1", kind: "text", ordinal: 1, text: "Direct support threads share the same message parts, participants, reactions, and read state as channels.", payload_json: null, file_ref: null, source_ref: null },
|
|
38
|
+
{ id: "part-dm-2", message_id: "message-dm-1", kind: "approval_prompt", ordinal: 2, text: null, payload_json: { component: "ApprovalPromptCard", tenant_ref: "local", session_ref: "dm-ai-devops", read_only: true, props: { action: "Dispatch worker", reason: "Requires audited approval backend before mutation", risk: "medium", requested_by: "AI DevOps", disabled: true } }, file_ref: null, source_ref: "tambo:seed" },
|
|
39
|
+
],
|
|
35
40
|
reactions: [],
|
|
36
41
|
read_states: [{ conversation_id: "dm-ai-devops", participant_id: "participant-dm-local", last_read_message_id: null, last_read_sequence: 0, updated_at: "2026-06-27T12:00:00Z" }],
|
|
37
42
|
},
|
|
@@ -37,7 +37,7 @@ export function renderDashboardHtml(status: GuiResponseEnvelope<GuiStatusData>):
|
|
|
37
37
|
.map((capability) => `<li>${escapeHtml(capability.label)}: ${escapeHtml(capability.status)}</li>`)
|
|
38
38
|
.join("");
|
|
39
39
|
const navSections = [
|
|
40
|
-
{ heading: "Development", items: ["AI Sessions", "Workers", "Local Repos", "Repos", "Remote Repos", "Deployments", "Secrets", "AI Providers"] },
|
|
40
|
+
{ heading: "Development", items: ["AI Sessions", "Pulse & Workers", "Local Repos", "Repos", "Remote Repos", "Deployments", "Secrets", "AI Providers"] },
|
|
41
41
|
{ heading: "Account", items: ["Help", "Settings", "Notifications", "Admin"] },
|
|
42
42
|
{ heading: "Operations", items: ["Dashboard", "Vault", "Agents file explorer", "Config", "Local Setup", "Routines"] },
|
|
43
43
|
{ heading: "Infrastructure", items: ["Devices", "VPNs & Proxies", "Apps", "Installation", "Registrars", "Hosts", "Servers"] },
|
|
@@ -99,6 +99,15 @@ function normalizeStatusEnvelope(envelope: GuiResponseEnvelope<Partial<GuiStatus
|
|
|
99
99
|
backups: { ...statusFixture.vault.backups, ...data.vault?.backups },
|
|
100
100
|
audit: { ...statusFixture.vault.audit, ...data.vault?.audit },
|
|
101
101
|
},
|
|
102
|
+
pulse_workers: {
|
|
103
|
+
...statusFixture.pulse_workers,
|
|
104
|
+
...data.pulse_workers,
|
|
105
|
+
kpis: data.pulse_workers?.kpis ?? statusFixture.pulse_workers.kpis,
|
|
106
|
+
attention: data.pulse_workers?.attention ?? statusFixture.pulse_workers.attention,
|
|
107
|
+
filters: { ...statusFixture.pulse_workers.filters, ...data.pulse_workers?.filters },
|
|
108
|
+
charts: data.pulse_workers?.charts ?? statusFixture.pulse_workers.charts,
|
|
109
|
+
events: data.pulse_workers?.events ?? statusFixture.pulse_workers.events,
|
|
110
|
+
},
|
|
102
111
|
capabilities: data.capabilities ?? statusFixture.capabilities,
|
|
103
112
|
secrets: data.secrets ?? statusFixture.secrets,
|
|
104
113
|
placeholders: data.placeholders ?? statusFixture.placeholders,
|
|
@@ -1562,6 +1562,48 @@ code {
|
|
|
1562
1562
|
border-style: dashed;
|
|
1563
1563
|
}
|
|
1564
1564
|
|
|
1565
|
+
.genui-card {
|
|
1566
|
+
background: var(--surface-elevated);
|
|
1567
|
+
border: 1px solid var(--border-accent);
|
|
1568
|
+
border-radius: 14px;
|
|
1569
|
+
display: grid;
|
|
1570
|
+
gap: 10px;
|
|
1571
|
+
margin-top: 4px;
|
|
1572
|
+
padding: 12px;
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
.genui-card-invalid {
|
|
1576
|
+
border-color: var(--border-warning);
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
.genui-card h3 {
|
|
1580
|
+
margin: 0;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
.genui-card dl {
|
|
1584
|
+
display: grid;
|
|
1585
|
+
gap: 8px;
|
|
1586
|
+
grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
|
|
1587
|
+
margin: 0;
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
.genui-card dt {
|
|
1591
|
+
color: var(--text-muted);
|
|
1592
|
+
font-size: 0.76rem;
|
|
1593
|
+
text-transform: uppercase;
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
.genui-card dd {
|
|
1597
|
+
color: var(--text-secondary);
|
|
1598
|
+
margin: 0;
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
.genui-deferred {
|
|
1602
|
+
border-top: 1px dashed var(--border);
|
|
1603
|
+
color: var(--text-muted);
|
|
1604
|
+
padding-top: 8px;
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1565
1607
|
.conversation-list-panel {
|
|
1566
1608
|
grid-template-rows: auto auto auto minmax(0, 1fr);
|
|
1567
1609
|
}
|
|
@@ -2179,6 +2221,157 @@ code {
|
|
|
2179
2221
|
margin-bottom: 16px;
|
|
2180
2222
|
}
|
|
2181
2223
|
|
|
2224
|
+
.pulse-workers-surface {
|
|
2225
|
+
display: grid;
|
|
2226
|
+
gap: 16px;
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
.pulse-hero {
|
|
2230
|
+
background:
|
|
2231
|
+
linear-gradient(135deg, color-mix(in srgb, var(--accent) 18%, transparent), transparent 64%),
|
|
2232
|
+
var(--surface-elevated);
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
.pulse-scope-strip,
|
|
2236
|
+
.pulse-filter-row {
|
|
2237
|
+
display: flex;
|
|
2238
|
+
flex-wrap: wrap;
|
|
2239
|
+
gap: 8px;
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
.pulse-scope-strip span,
|
|
2243
|
+
.pulse-filter-row button {
|
|
2244
|
+
background: var(--surface-muted);
|
|
2245
|
+
border: 1px solid var(--border);
|
|
2246
|
+
border-radius: 999px;
|
|
2247
|
+
color: var(--text-secondary);
|
|
2248
|
+
padding: 7px 10px;
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
.pulse-scope-strip strong {
|
|
2252
|
+
color: var(--accent);
|
|
2253
|
+
margin-right: 6px;
|
|
2254
|
+
text-transform: uppercase;
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
.pulse-kpi-grid,
|
|
2258
|
+
.pulse-layout {
|
|
2259
|
+
display: grid;
|
|
2260
|
+
gap: 12px;
|
|
2261
|
+
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
.pulse-kpi-card {
|
|
2265
|
+
display: grid;
|
|
2266
|
+
gap: 8px;
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
.pulse-kpi-card span,
|
|
2270
|
+
.pulse-chart-panel p,
|
|
2271
|
+
.pulse-drilldown-panel p,
|
|
2272
|
+
.pulse-actions-panel p {
|
|
2273
|
+
color: var(--text-secondary);
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
.pulse-kpi-card strong {
|
|
2277
|
+
color: var(--accent);
|
|
2278
|
+
font-size: clamp(2rem, 5vw, 3rem);
|
|
2279
|
+
letter-spacing: -0.08em;
|
|
2280
|
+
line-height: 1;
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
.pulse-attention-panel,
|
|
2284
|
+
.pulse-chart-panel,
|
|
2285
|
+
.pulse-activity-panel,
|
|
2286
|
+
.pulse-drilldown-panel,
|
|
2287
|
+
.pulse-actions-panel {
|
|
2288
|
+
display: grid;
|
|
2289
|
+
gap: 12px;
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
.pulse-attention-panel ul {
|
|
2293
|
+
display: grid;
|
|
2294
|
+
gap: 8px;
|
|
2295
|
+
margin: 0;
|
|
2296
|
+
padding-left: 18px;
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
.pulse-chart-placeholder {
|
|
2300
|
+
align-items: end;
|
|
2301
|
+
background: var(--surface-muted);
|
|
2302
|
+
border: 1px solid var(--border);
|
|
2303
|
+
border-radius: 16px;
|
|
2304
|
+
display: grid;
|
|
2305
|
+
gap: 8px;
|
|
2306
|
+
grid-template-columns: repeat(6, minmax(0, 1fr));
|
|
2307
|
+
min-height: 180px;
|
|
2308
|
+
padding: 14px;
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
.pulse-chart-placeholder span {
|
|
2312
|
+
align-items: end;
|
|
2313
|
+
background: color-mix(in srgb, var(--accent) 26%, transparent);
|
|
2314
|
+
border: 1px solid var(--border-accent);
|
|
2315
|
+
border-radius: 12px 12px 4px 4px;
|
|
2316
|
+
color: var(--accent-strong);
|
|
2317
|
+
display: flex;
|
|
2318
|
+
font-size: 0.75rem;
|
|
2319
|
+
font-weight: 900;
|
|
2320
|
+
justify-content: center;
|
|
2321
|
+
min-height: calc(40px + var(--bar-index, 1) * 18px);
|
|
2322
|
+
padding: 8px 4px;
|
|
2323
|
+
text-transform: uppercase;
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
.pulse-chart-placeholder span:nth-child(2) { --bar-index: 3; }
|
|
2327
|
+
.pulse-chart-placeholder span:nth-child(3) { --bar-index: 5; }
|
|
2328
|
+
.pulse-chart-placeholder span:nth-child(4) { --bar-index: 2; }
|
|
2329
|
+
.pulse-chart-placeholder span:nth-child(5) { --bar-index: 4; }
|
|
2330
|
+
.pulse-chart-placeholder span:nth-child(6) { --bar-index: 3; }
|
|
2331
|
+
|
|
2332
|
+
.pulse-activity-table {
|
|
2333
|
+
background: var(--surface-muted);
|
|
2334
|
+
border: 1px solid var(--border);
|
|
2335
|
+
border-radius: 16px;
|
|
2336
|
+
display: grid;
|
|
2337
|
+
overflow: hidden;
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
.pulse-activity-table thead,
|
|
2341
|
+
.pulse-activity-table tbody {
|
|
2342
|
+
display: grid;
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
.pulse-activity-row {
|
|
2346
|
+
display: grid;
|
|
2347
|
+
gap: 10px;
|
|
2348
|
+
grid-template-columns: 0.7fr 1fr 1.4fr 1fr 1.2fr 1.2fr;
|
|
2349
|
+
padding: 12px 14px;
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
.pulse-activity-row + .pulse-activity-row {
|
|
2353
|
+
border-top: 1px solid var(--border);
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
.pulse-activity-row th,
|
|
2357
|
+
.pulse-activity-row td {
|
|
2358
|
+
font-weight: inherit;
|
|
2359
|
+
text-align: left;
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
.pulse-activity-header {
|
|
2363
|
+
background: color-mix(in srgb, var(--accent) 10%, transparent);
|
|
2364
|
+
color: var(--text-secondary);
|
|
2365
|
+
font-size: 0.78rem;
|
|
2366
|
+
font-weight: 900;
|
|
2367
|
+
text-transform: uppercase;
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
.pulse-actions-panel button,
|
|
2371
|
+
.pulse-attention-panel button {
|
|
2372
|
+
justify-self: start;
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2182
2375
|
.apps-surface .compact-notice {
|
|
2183
2376
|
margin-bottom: 0;
|
|
2184
2377
|
}
|
|
@@ -3624,10 +3817,25 @@ button.managed-toggle:focus-visible {
|
|
|
3624
3817
|
.data-row,
|
|
3625
3818
|
.editable-row,
|
|
3626
3819
|
.managed-app-details,
|
|
3820
|
+
.pulse-activity-row,
|
|
3627
3821
|
.provider-account-list li {
|
|
3628
3822
|
grid-template-columns: 1fr;
|
|
3629
3823
|
}
|
|
3630
3824
|
|
|
3825
|
+
.pulse-activity-header {
|
|
3826
|
+
display: none;
|
|
3827
|
+
}
|
|
3828
|
+
|
|
3829
|
+
.pulse-activity-row {
|
|
3830
|
+
background: var(--surface-elevated);
|
|
3831
|
+
}
|
|
3832
|
+
|
|
3833
|
+
.pulse-activity-row td::before {
|
|
3834
|
+
color: var(--text-muted);
|
|
3835
|
+
content: attr(data-label) ": ";
|
|
3836
|
+
font-weight: 900;
|
|
3837
|
+
}
|
|
3838
|
+
|
|
3631
3839
|
.pill-tabs {
|
|
3632
3840
|
width: 100%;
|
|
3633
3841
|
}
|
package/setup.sh
CHANGED
|
@@ -12,7 +12,7 @@ shopt -s inherit_errexit 2>/dev/null || true
|
|
|
12
12
|
# AI Assistant Server Access Framework Setup Script
|
|
13
13
|
# Helps developers set up the framework for their infrastructure
|
|
14
14
|
#
|
|
15
|
-
# Version: 3.29.
|
|
15
|
+
# Version: 3.29.39
|
|
16
16
|
#
|
|
17
17
|
# Quick Install:
|
|
18
18
|
# npm install -g aidevops && aidevops update (recommended)
|