@useorgx/openclaw-plugin 0.4.8 → 0.4.9

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.
Files changed (125) hide show
  1. package/dashboard/dist/assets/B5NEElEI.css +1 -0
  2. package/dashboard/dist/assets/BhapSNAs.js +215 -0
  3. package/dashboard/dist/assets/{BNeJ0kpF.js → iFdvE7lx.js} +1 -1
  4. package/dashboard/dist/assets/{CUV9IHHi.js → jRJsmpYM.js} +1 -1
  5. package/dashboard/dist/index.html +2 -2
  6. package/dist/activity-store.js +4 -18
  7. package/dist/agent-context-store.js +5 -25
  8. package/dist/agent-run-store.js +5 -25
  9. package/dist/agent-suite.js +1 -8
  10. package/dist/auth/flows.d.ts +47 -0
  11. package/dist/auth/flows.js +169 -0
  12. package/dist/auth-store.js +6 -26
  13. package/dist/byok-store.js +5 -19
  14. package/dist/cli/orgx.d.ts +66 -0
  15. package/dist/cli/orgx.js +91 -0
  16. package/dist/config/refresh.d.ts +32 -0
  17. package/dist/config/refresh.js +55 -0
  18. package/dist/config/resolution.d.ts +37 -0
  19. package/dist/config/resolution.js +178 -0
  20. package/dist/contracts/shared-types.d.ts +147 -0
  21. package/dist/contracts/shared-types.js +3 -0
  22. package/dist/contracts/types.d.ts +1 -134
  23. package/dist/contracts/types.js +5 -0
  24. package/dist/entities/auto-assignment.d.ts +36 -0
  25. package/dist/entities/auto-assignment.js +115 -0
  26. package/dist/entity-comment-store.js +5 -25
  27. package/dist/hash-utils.d.ts +2 -0
  28. package/dist/hash-utils.js +12 -0
  29. package/dist/http/helpers/activity-headline.d.ts +10 -0
  30. package/dist/http/helpers/activity-headline.js +192 -0
  31. package/dist/http/helpers/artifact-fallback.d.ts +13 -0
  32. package/dist/http/helpers/artifact-fallback.js +148 -0
  33. package/dist/http/helpers/auto-continue-engine.d.ts +298 -0
  34. package/dist/http/helpers/auto-continue-engine.js +1218 -0
  35. package/dist/http/helpers/autopilot-operations.d.ts +157 -0
  36. package/dist/http/helpers/autopilot-operations.js +403 -0
  37. package/dist/http/helpers/autopilot-runtime.d.ts +42 -0
  38. package/dist/http/helpers/autopilot-runtime.js +319 -0
  39. package/dist/http/helpers/autopilot-slice-utils.d.ts +38 -0
  40. package/dist/http/helpers/autopilot-slice-utils.js +476 -0
  41. package/dist/http/helpers/decision-mapper.d.ts +12 -0
  42. package/dist/http/helpers/decision-mapper.js +44 -0
  43. package/dist/http/helpers/dispatch-lifecycle.d.ts +102 -0
  44. package/dist/http/helpers/dispatch-lifecycle.js +604 -0
  45. package/dist/http/helpers/hash-utils.d.ts +1 -0
  46. package/dist/http/helpers/hash-utils.js +1 -0
  47. package/dist/http/helpers/kickoff-context.d.ts +12 -0
  48. package/dist/http/helpers/kickoff-context.js +154 -0
  49. package/dist/http/helpers/mission-control.d.ts +94 -0
  50. package/dist/http/helpers/mission-control.js +894 -0
  51. package/dist/http/helpers/openclaw-cli.d.ts +37 -0
  52. package/dist/http/helpers/openclaw-cli.js +283 -0
  53. package/dist/http/helpers/runtime-sse.d.ts +20 -0
  54. package/dist/http/helpers/runtime-sse.js +110 -0
  55. package/dist/http/helpers/value-utils.d.ts +6 -0
  56. package/dist/http/helpers/value-utils.js +67 -0
  57. package/dist/http/index.d.ts +88 -0
  58. package/dist/http/index.js +2353 -0
  59. package/dist/http/router.d.ts +23 -0
  60. package/dist/http/router.js +23 -0
  61. package/dist/http/routes/agent-control.d.ts +79 -0
  62. package/dist/http/routes/agent-control.js +684 -0
  63. package/dist/http/routes/agent-suite.d.ts +29 -0
  64. package/dist/http/routes/agent-suite.js +198 -0
  65. package/dist/http/routes/agents-catalog.d.ts +40 -0
  66. package/dist/http/routes/agents-catalog.js +83 -0
  67. package/dist/http/routes/billing.d.ts +23 -0
  68. package/dist/http/routes/billing.js +55 -0
  69. package/dist/http/routes/debug.d.ts +14 -0
  70. package/dist/http/routes/debug.js +21 -0
  71. package/dist/http/routes/decision-actions.d.ts +13 -0
  72. package/dist/http/routes/decision-actions.js +66 -0
  73. package/dist/http/routes/delegation.d.ts +19 -0
  74. package/dist/http/routes/delegation.js +32 -0
  75. package/dist/http/routes/entities.d.ts +47 -0
  76. package/dist/http/routes/entities.js +152 -0
  77. package/dist/http/routes/entity-dynamic.d.ts +25 -0
  78. package/dist/http/routes/entity-dynamic.js +191 -0
  79. package/dist/http/routes/health.d.ts +22 -0
  80. package/dist/http/routes/health.js +49 -0
  81. package/dist/http/routes/live-legacy.d.ts +110 -0
  82. package/dist/http/routes/live-legacy.js +598 -0
  83. package/dist/http/routes/live-misc.d.ts +69 -0
  84. package/dist/http/routes/live-misc.js +206 -0
  85. package/dist/http/routes/live-snapshot.d.ts +90 -0
  86. package/dist/http/routes/live-snapshot.js +297 -0
  87. package/dist/http/routes/mission-control-actions.d.ts +83 -0
  88. package/dist/http/routes/mission-control-actions.js +541 -0
  89. package/dist/http/routes/mission-control-read.d.ts +28 -0
  90. package/dist/http/routes/mission-control-read.js +67 -0
  91. package/dist/http/routes/onboarding.d.ts +34 -0
  92. package/dist/http/routes/onboarding.js +101 -0
  93. package/dist/http/routes/run-control.d.ts +24 -0
  94. package/dist/http/routes/run-control.js +86 -0
  95. package/dist/http/routes/runtime-hooks.d.ts +69 -0
  96. package/dist/http/routes/runtime-hooks.js +437 -0
  97. package/dist/http/routes/settings-byok.d.ts +23 -0
  98. package/dist/http/routes/settings-byok.js +163 -0
  99. package/dist/http/routes/summary.d.ts +18 -0
  100. package/dist/http/routes/summary.js +42 -0
  101. package/dist/http/routes/work-artifacts.d.ts +9 -0
  102. package/dist/http/routes/work-artifacts.js +36 -0
  103. package/dist/http/shared-state.d.ts +16 -0
  104. package/dist/http/shared-state.js +1 -0
  105. package/dist/http-handler.d.ts +1 -88
  106. package/dist/http-handler.js +1 -10605
  107. package/dist/index.js +108 -2243
  108. package/dist/json-utils.d.ts +1 -0
  109. package/dist/json-utils.js +8 -0
  110. package/dist/next-up-queue-store.js +4 -18
  111. package/dist/runtime-instance-store.js +5 -31
  112. package/dist/services/background.d.ts +23 -0
  113. package/dist/services/background.js +23 -0
  114. package/dist/services/instrumentation.d.ts +29 -0
  115. package/dist/services/instrumentation.js +136 -0
  116. package/dist/snapshot-store.js +5 -25
  117. package/dist/stores/json-store.d.ts +11 -0
  118. package/dist/stores/json-store.js +42 -0
  119. package/dist/sync/outbox-replay.d.ts +55 -0
  120. package/dist/sync/outbox-replay.js +514 -0
  121. package/dist/tools/core-tools.d.ts +76 -0
  122. package/dist/tools/core-tools.js +1005 -0
  123. package/package.json +1 -1
  124. package/dashboard/dist/assets/BzkiMPmM.js +0 -215
  125. package/dashboard/dist/assets/Ie7d9Iq2.css +0 -1
@@ -4,6 +4,7 @@
4
4
  * Types for the plugin's API client and tool interfaces.
5
5
  * Mirrors the server-side types in orgx/lib/client-integration/types.ts
6
6
  */
7
+ export type { HandoffEvent, HandoffSummary, LiveActivityItem, LiveActivityType, LiveDecision, OnboardingKeySource, OnboardingNextAction, OnboardingState, OnboardingStatus, RunPhase, RuntimeInstance, RuntimeInstanceState, RuntimeProviderLogo, RuntimeSourceClient, SessionTreeEdge, SessionTreeGroup, SessionTreeNode, SessionTreeResponse, } from './shared-types.js';
7
8
  export interface OrgXConfig {
8
9
  /** OrgX API key */
9
10
  apiKey: string;
@@ -24,23 +25,6 @@ export interface OrgXConfig {
24
25
  */
25
26
  autoInstallAgentSuiteOnConnect?: boolean;
26
27
  }
27
- export type OnboardingStatus = 'idle' | 'starting' | 'awaiting_browser_auth' | 'pairing' | 'connected' | 'error' | 'manual_key';
28
- export type OnboardingNextAction = 'connect' | 'wait_for_browser' | 'open_dashboard' | 'enter_manual_key' | 'retry' | 'reconnect';
29
- export interface OnboardingState {
30
- status: OnboardingStatus;
31
- hasApiKey: boolean;
32
- connectionVerified: boolean;
33
- workspaceName: string | null;
34
- lastError: string | null;
35
- nextAction: OnboardingNextAction;
36
- docsUrl: string;
37
- keySource: 'config' | 'environment' | 'persisted' | 'openclaw-config-file' | 'legacy-dev' | 'none';
38
- installationId: string | null;
39
- connectUrl: string | null;
40
- pairingId: string | null;
41
- expiresAt: string | null;
42
- pollIntervalMs: number | null;
43
- }
44
28
  export interface OrgSnapshot {
45
29
  /** Active initiatives */
46
30
  initiatives: Initiative[];
@@ -265,7 +249,6 @@ export interface BillingUrlResult {
265
249
  url: string | null;
266
250
  checkout_url?: string | null;
267
251
  }
268
- export type RunPhase = 'intent' | 'execution' | 'blocked' | 'review' | 'handoff' | 'completed';
269
252
  export interface HandoffWorkspaceState {
270
253
  git?: {
271
254
  branch?: string | null;
@@ -397,7 +380,6 @@ export interface EntityListFilters {
397
380
  [key: string]: unknown;
398
381
  }
399
382
  export type ReportingSourceClient = 'openclaw' | 'codex' | 'claude-code' | 'api';
400
- export type RuntimeSourceClient = ReportingSourceClient | 'unknown';
401
383
  export type ReportingPhase = 'intent' | 'execution' | 'blocked' | 'review' | 'handoff' | 'completed';
402
384
  export type ReportingLevel = 'info' | 'warn' | 'error';
403
385
  export type TaskStatus = 'todo' | 'in_progress' | 'done' | 'blocked';
@@ -538,118 +520,3 @@ export interface RecordRunRetroResponse {
538
520
  event_id: string | null;
539
521
  auth_mode?: 'service' | 'api_key';
540
522
  }
541
- export type LiveActivityType = 'run_started' | 'run_completed' | 'run_failed' | 'artifact_created' | 'decision_requested' | 'decision_resolved' | 'handoff_requested' | 'handoff_claimed' | 'handoff_fulfilled' | 'blocker_created' | 'milestone_completed' | 'delegation';
542
- export type RuntimeInstanceState = 'active' | 'stale' | 'stopped' | 'error';
543
- export interface RuntimeInstance {
544
- id: string;
545
- sourceClient: RuntimeSourceClient;
546
- displayName: string;
547
- providerLogo: 'codex' | 'openai' | 'anthropic' | 'openclaw' | 'orgx' | 'unknown';
548
- state: RuntimeInstanceState;
549
- runId: string | null;
550
- correlationId: string | null;
551
- initiativeId: string | null;
552
- workstreamId: string | null;
553
- taskId: string | null;
554
- agentId: string | null;
555
- agentName: string | null;
556
- phase: string | null;
557
- progressPct: number | null;
558
- currentTask: string | null;
559
- lastHeartbeatAt: string | null;
560
- lastEventAt: string;
561
- lastMessage: string | null;
562
- metadata: Record<string, unknown> | null;
563
- }
564
- export interface LiveActivityItem {
565
- id: string;
566
- type: LiveActivityType;
567
- title: string;
568
- description: string | null;
569
- agentId: string | null;
570
- agentName: string | null;
571
- requesterAgentId: string | null;
572
- requesterAgentName: string | null;
573
- executorAgentId: string | null;
574
- executorAgentName: string | null;
575
- runId: string | null;
576
- initiativeId: string | null;
577
- timestamp: string;
578
- phase?: RunPhase | null;
579
- state?: string | null;
580
- kind?: string | null;
581
- summary?: string | null;
582
- decisionRequired?: boolean;
583
- costDelta?: number | null;
584
- runtimeClient?: RuntimeSourceClient | null;
585
- runtimeLabel?: string | null;
586
- runtimeProvider?: RuntimeInstance['providerLogo'] | null;
587
- instanceId?: string | null;
588
- lastHeartbeatAt?: string | null;
589
- metadata?: Record<string, unknown>;
590
- }
591
- export interface SessionTreeNode {
592
- id: string;
593
- parentId: string | null;
594
- runId: string;
595
- title: string;
596
- agentId: string | null;
597
- agentName: string | null;
598
- status: string;
599
- progress: number | null;
600
- initiativeId: string | null;
601
- workstreamId: string | null;
602
- groupId: string;
603
- groupLabel: string;
604
- startedAt: string | null;
605
- updatedAt: string | null;
606
- lastEventAt: string | null;
607
- lastEventSummary: string | null;
608
- blockers: string[];
609
- phase?: RunPhase | null;
610
- state?: string | null;
611
- eta?: string | null;
612
- cost?: number | null;
613
- checkpointCount?: number | null;
614
- blockerReason?: string | null;
615
- runtimeClient?: RuntimeSourceClient | null;
616
- runtimeLabel?: string | null;
617
- runtimeProvider?: RuntimeInstance['providerLogo'] | null;
618
- instanceId?: string | null;
619
- lastHeartbeatAt?: string | null;
620
- }
621
- export interface SessionTreeEdge {
622
- parentId: string;
623
- childId: string;
624
- }
625
- export interface SessionTreeGroup {
626
- id: string;
627
- label: string;
628
- status: string | null;
629
- }
630
- export interface SessionTreeResponse {
631
- nodes: SessionTreeNode[];
632
- edges: SessionTreeEdge[];
633
- groups: SessionTreeGroup[];
634
- }
635
- export interface HandoffEvent {
636
- id: string;
637
- handoffId: string;
638
- eventType: string;
639
- actorType: string | null;
640
- actorId: string | null;
641
- payload: Record<string, unknown> | null;
642
- createdAt: string;
643
- }
644
- export interface HandoffSummary {
645
- id: string;
646
- title: string;
647
- status: string;
648
- priority: string | null;
649
- summary: string | null;
650
- currentActorType: string | null;
651
- currentActorId: string | null;
652
- createdAt: string;
653
- updatedAt: string;
654
- events: HandoffEvent[];
655
- }
@@ -5,3 +5,8 @@
5
5
  * Mirrors the server-side types in orgx/lib/client-integration/types.ts
6
6
  */
7
7
  export {};
8
+ // =============================================================================
9
+ // LIVE SESSION GRAPH + HANDOFFS
10
+ // =============================================================================
11
+ // Live/session/handoff activity shapes are imported and re-exported from
12
+ // ./shared-types.ts to keep dashboard and plugin contracts in lockstep.
@@ -0,0 +1,36 @@
1
+ export type AutoAssignedAgent = {
2
+ id: string;
3
+ name: string;
4
+ domain: string | null;
5
+ };
6
+ export declare function autoAssignEntityForCreate(input: {
7
+ client: {
8
+ getLiveAgents: (payload: {
9
+ initiative: string | null;
10
+ includeIdle: boolean;
11
+ }) => Promise<{
12
+ agents?: unknown[];
13
+ }>;
14
+ delegationPreflight: (payload: {
15
+ intent: string;
16
+ }) => Promise<{
17
+ data?: {
18
+ recommended_split?: Array<{
19
+ owner_domain?: string | null;
20
+ }>;
21
+ };
22
+ }>;
23
+ updateEntity: (entityType: string, entityId: string, payload: Record<string, unknown>) => Promise<unknown>;
24
+ };
25
+ toErrorMessage: (err: unknown) => string;
26
+ entityType: string;
27
+ entityId: string;
28
+ initiativeId: string | null;
29
+ title: string;
30
+ summary: string | null;
31
+ }): Promise<{
32
+ assignmentSource: "orchestrator" | "fallback" | "manual";
33
+ assignedAgents: AutoAssignedAgent[];
34
+ warnings: string[];
35
+ updatedEntity: Record<string, unknown> | null;
36
+ }>;
@@ -0,0 +1,115 @@
1
+ export async function autoAssignEntityForCreate(input) {
2
+ const warnings = [];
3
+ const byKey = new Map();
4
+ const addAgent = (agent) => {
5
+ const key = `${agent.id}:${agent.name}`.toLowerCase();
6
+ if (!byKey.has(key))
7
+ byKey.set(key, agent);
8
+ };
9
+ let liveAgents = [];
10
+ try {
11
+ const agentResp = await input.client.getLiveAgents({
12
+ initiative: input.initiativeId,
13
+ includeIdle: true,
14
+ });
15
+ liveAgents = (Array.isArray(agentResp.agents) ? agentResp.agents : [])
16
+ .map((raw) => {
17
+ if (!raw || typeof raw !== "object")
18
+ return null;
19
+ const record = raw;
20
+ const id = (typeof record.id === "string" && record.id.trim()) ||
21
+ (typeof record.agentId === "string" && record.agentId.trim()) ||
22
+ "";
23
+ const name = (typeof record.name === "string" && record.name.trim()) ||
24
+ (typeof record.agentName === "string" && record.agentName.trim()) ||
25
+ id;
26
+ if (!name)
27
+ return null;
28
+ return {
29
+ id: id || `name:${name}`,
30
+ name,
31
+ domain: (typeof record.domain === "string" && record.domain.trim()) ||
32
+ (typeof record.role === "string" && record.role.trim()) ||
33
+ null,
34
+ status: (typeof record.status === "string" && record.status.trim()) || null,
35
+ };
36
+ })
37
+ .filter((agent) => agent !== null);
38
+ }
39
+ catch (err) {
40
+ warnings.push(`live agents unavailable (${input.toErrorMessage(err)})`);
41
+ }
42
+ const orchestrator = liveAgents.find((agent) => /holt|orchestrator/i.test(agent.name) ||
43
+ /orchestrator/i.test(agent.domain ?? ""));
44
+ if (orchestrator)
45
+ addAgent(orchestrator);
46
+ let assignmentSource = "fallback";
47
+ try {
48
+ const preflight = await input.client.delegationPreflight({
49
+ intent: `${input.title}${input.summary ? `: ${input.summary}` : ""}`,
50
+ });
51
+ const recommendations = preflight.data?.recommended_split ?? [];
52
+ const recommendedDomains = [
53
+ ...new Set(recommendations
54
+ .map((entry) => String(entry.owner_domain ?? "").trim().toLowerCase())
55
+ .filter(Boolean)),
56
+ ];
57
+ for (const domain of recommendedDomains) {
58
+ const match = liveAgents.find((agent) => (agent.domain ?? "").toLowerCase().includes(domain));
59
+ if (match)
60
+ addAgent(match);
61
+ }
62
+ if (recommendedDomains.length > 0) {
63
+ assignmentSource = "orchestrator";
64
+ }
65
+ }
66
+ catch (err) {
67
+ warnings.push(`delegation preflight failed (${input.toErrorMessage(err)})`);
68
+ }
69
+ if (byKey.size === 0) {
70
+ const haystack = `${input.title} ${input.summary ?? ""}`.toLowerCase();
71
+ const domainHints = [];
72
+ if (/market|campaign|thread|article|tweet|copy/.test(haystack)) {
73
+ domainHints.push("marketing");
74
+ }
75
+ else if (/design|ux|ui|a11y/.test(haystack)) {
76
+ domainHints.push("design");
77
+ }
78
+ else if (/ops|runbook|incident|reliability/.test(haystack)) {
79
+ domainHints.push("operations");
80
+ }
81
+ else if (/sales|deal|pipeline/.test(haystack)) {
82
+ domainHints.push("sales");
83
+ }
84
+ else {
85
+ domainHints.push("engineering", "product");
86
+ }
87
+ for (const domain of domainHints) {
88
+ const match = liveAgents.find((agent) => (agent.domain ?? "").toLowerCase().includes(domain));
89
+ if (match)
90
+ addAgent(match);
91
+ }
92
+ }
93
+ if (byKey.size === 0 && liveAgents.length > 0) {
94
+ addAgent(liveAgents[0]);
95
+ warnings.push("fallback selected first available live agent");
96
+ }
97
+ const assignedAgents = Array.from(byKey.values());
98
+ let updatedEntity = null;
99
+ try {
100
+ updatedEntity = (await input.client.updateEntity(input.entityType, input.entityId, {
101
+ assigned_agent_ids: assignedAgents.map((agent) => agent.id),
102
+ assigned_agent_names: assignedAgents.map((agent) => agent.name),
103
+ assignment_source: assignmentSource,
104
+ }));
105
+ }
106
+ catch (err) {
107
+ warnings.push(`assignment update failed (${input.toErrorMessage(err)})`);
108
+ }
109
+ return {
110
+ assignmentSource,
111
+ assignedAgents,
112
+ warnings,
113
+ updatedEntity,
114
+ };
115
+ }
@@ -1,7 +1,8 @@
1
- import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, } from "node:fs";
1
+ import { existsSync, readFileSync, } from "node:fs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { getOrgxPluginConfigDir, getOrgxPluginConfigPath } from "./paths.js";
4
4
  import { backupCorruptFileSync, writeJsonFileAtomicSync } from "./fs-utils.js";
5
+ import { clearStoreFileSync, ensureStoreDirSync, parseJsonSafe, } from "./stores/json-store.js";
5
6
  const MAX_COMMENTS_PER_ENTITY = 240;
6
7
  const MAX_TOTAL_COMMENTS = 1_500;
7
8
  function commentsDir() {
@@ -11,22 +12,7 @@ function commentsFile() {
11
12
  return getOrgxPluginConfigPath("entity-comments.json");
12
13
  }
13
14
  function ensureDir() {
14
- const dir = commentsDir();
15
- mkdirSync(dir, { recursive: true, mode: 0o700 });
16
- try {
17
- chmodSync(dir, 0o700);
18
- }
19
- catch {
20
- // best effort
21
- }
22
- }
23
- function parseJson(value) {
24
- try {
25
- return JSON.parse(value);
26
- }
27
- catch {
28
- return null;
29
- }
15
+ ensureStoreDirSync(commentsDir());
30
16
  }
31
17
  function entityKey(entityType, entityId) {
32
18
  return `${entityType.trim().toLowerCase()}:${entityId.trim()}`;
@@ -63,7 +49,7 @@ function readStore() {
63
49
  return { updatedAt: new Date().toISOString(), commentsByEntity: {} };
64
50
  }
65
51
  const raw = readFileSync(file, "utf8");
66
- const parsed = parseJson(raw);
52
+ const parsed = parseJsonSafe(raw);
67
53
  if (!parsed || typeof parsed !== "object") {
68
54
  backupCorruptFileSync(file);
69
55
  return { updatedAt: new Date().toISOString(), commentsByEntity: {} };
@@ -180,11 +166,5 @@ export function mergeEntityComments(remote, local) {
180
166
  return list;
181
167
  }
182
168
  export function clearEntityCommentsStore() {
183
- const file = commentsFile();
184
- try {
185
- rmSync(file, { force: true });
186
- }
187
- catch {
188
- // best effort
189
- }
169
+ clearStoreFileSync(commentsFile());
190
170
  }
@@ -0,0 +1,2 @@
1
+ export declare function stableHash(value: string): string;
2
+ export declare function idempotencyKey(parts: Array<string | null | undefined>): string;
@@ -0,0 +1,12 @@
1
+ import { createHash } from "node:crypto";
2
+ export function stableHash(value) {
3
+ return createHash("sha256").update(value).digest("hex");
4
+ }
5
+ export function idempotencyKey(parts) {
6
+ const raw = parts
7
+ .filter((part) => typeof part === "string" && part.length > 0)
8
+ .join(":");
9
+ const cleaned = raw.replace(/[^a-zA-Z0-9:_-]/g, "-").slice(0, 84);
10
+ const suffix = stableHash(raw).slice(0, 20);
11
+ return `${cleaned}:${suffix}`.slice(0, 120);
12
+ }
@@ -0,0 +1,10 @@
1
+ export type ActivityHeadlineSource = "llm" | "heuristic";
2
+ export declare function summarizeActivityHeadline(input: {
3
+ text: string;
4
+ title?: string | null;
5
+ type?: string | null;
6
+ }): Promise<{
7
+ headline: string;
8
+ source: ActivityHeadlineSource;
9
+ model: string | null;
10
+ }>;
@@ -0,0 +1,192 @@
1
+ import { createHash } from "node:crypto";
2
+ import { pickString } from "./value-utils.js";
3
+ const ACTIVITY_HEADLINE_TIMEOUT_MS = 4_000;
4
+ const ACTIVITY_HEADLINE_CACHE_TTL_MS = 12 * 60 * 60_000;
5
+ const ACTIVITY_HEADLINE_CACHE_MAX = 1_000;
6
+ const ACTIVITY_HEADLINE_MAX_INPUT_CHARS = 8_000;
7
+ const DEFAULT_ACTIVITY_HEADLINE_MODEL = "openai/gpt-4.1-nano";
8
+ const activityHeadlineCache = new Map();
9
+ let resolvedActivitySummaryApiKey;
10
+ function normalizeSpaces(value) {
11
+ return value.replace(/\s+/g, " ").trim();
12
+ }
13
+ function stripMarkdownLite(value) {
14
+ return value
15
+ .replace(/```[\s\S]*?```/g, " ")
16
+ .replace(/`([^`]+)`/g, "$1")
17
+ .replace(/\*\*([^*]+)\*\*/g, "$1")
18
+ .replace(/__([^_]+)__/g, "$1")
19
+ .replace(/\*([^*\n]+)\*/g, "$1")
20
+ .replace(/_([^_\n]+)_/g, "$1")
21
+ .replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, "$1")
22
+ .replace(/^\s{0,3}#{1,6}\s+/gm, "")
23
+ .replace(/^\s*[-*]\s+/gm, "")
24
+ .replace(/^\s*\d+\.\s+/gm, "")
25
+ .replace(/\r\n/g, "\n")
26
+ .trim();
27
+ }
28
+ function cleanActivityHeadline(value) {
29
+ const lines = stripMarkdownLite(value)
30
+ .split("\n")
31
+ .map((line) => normalizeSpaces(line))
32
+ .filter((line) => line.length > 0 && !/^\|?[:\-| ]+\|?$/.test(line));
33
+ const headline = lines[0] ?? "";
34
+ if (!headline)
35
+ return "";
36
+ if (headline.length <= 108)
37
+ return headline;
38
+ return `${headline.slice(0, 107).trimEnd()}…`;
39
+ }
40
+ function heuristicActivityHeadline(text, title) {
41
+ const cleanedText = cleanActivityHeadline(text);
42
+ if (cleanedText.length > 0)
43
+ return cleanedText;
44
+ const cleanedTitle = cleanActivityHeadline(title ?? "");
45
+ if (cleanedTitle.length > 0)
46
+ return cleanedTitle;
47
+ return "Activity update";
48
+ }
49
+ function resolveActivitySummaryApiKey() {
50
+ if (resolvedActivitySummaryApiKey !== undefined) {
51
+ return resolvedActivitySummaryApiKey;
52
+ }
53
+ const candidates = [
54
+ process.env.ORGX_ACTIVITY_SUMMARY_API_KEY ?? "",
55
+ process.env.OPENROUTER_API_KEY ?? "",
56
+ ];
57
+ const key = candidates.find((candidate) => candidate.trim().length > 0)?.trim() ?? "";
58
+ resolvedActivitySummaryApiKey = key || null;
59
+ return resolvedActivitySummaryApiKey;
60
+ }
61
+ function trimActivityHeadlineCache() {
62
+ while (activityHeadlineCache.size > ACTIVITY_HEADLINE_CACHE_MAX) {
63
+ const firstKey = activityHeadlineCache.keys().next().value;
64
+ if (!firstKey)
65
+ break;
66
+ activityHeadlineCache.delete(firstKey);
67
+ }
68
+ }
69
+ function extractCompletionText(payload) {
70
+ const choices = payload.choices;
71
+ if (!Array.isArray(choices) || choices.length === 0)
72
+ return null;
73
+ const first = choices[0];
74
+ if (!first || typeof first !== "object")
75
+ return null;
76
+ const firstRecord = first;
77
+ const message = firstRecord.message;
78
+ if (message && typeof message === "object") {
79
+ const content = message.content;
80
+ if (typeof content === "string") {
81
+ return content;
82
+ }
83
+ if (Array.isArray(content)) {
84
+ const textParts = content
85
+ .map((part) => {
86
+ if (typeof part === "string")
87
+ return part;
88
+ if (!part || typeof part !== "object")
89
+ return "";
90
+ const record = part;
91
+ return typeof record.text === "string" ? record.text : "";
92
+ })
93
+ .filter((part) => part.length > 0);
94
+ if (textParts.length > 0) {
95
+ return textParts.join(" ");
96
+ }
97
+ }
98
+ }
99
+ return pickString(firstRecord, ["text", "content"]);
100
+ }
101
+ export async function summarizeActivityHeadline(input) {
102
+ const normalizedText = normalizeSpaces(input.text).slice(0, ACTIVITY_HEADLINE_MAX_INPUT_CHARS);
103
+ const normalizedTitle = normalizeSpaces(input.title ?? "");
104
+ const normalizedType = normalizeSpaces(input.type ?? "");
105
+ const heuristic = heuristicActivityHeadline(normalizedText, normalizedTitle);
106
+ const cacheKey = createHash("sha256")
107
+ .update(`${normalizedType}\n${normalizedTitle}\n${normalizedText}`)
108
+ .digest("hex");
109
+ const cached = activityHeadlineCache.get(cacheKey);
110
+ if (cached && cached.expiresAt > Date.now()) {
111
+ return { headline: cached.headline, source: cached.source, model: null };
112
+ }
113
+ const apiKey = resolveActivitySummaryApiKey();
114
+ if (!apiKey) {
115
+ activityHeadlineCache.set(cacheKey, {
116
+ headline: heuristic,
117
+ source: "heuristic",
118
+ expiresAt: Date.now() + ACTIVITY_HEADLINE_CACHE_TTL_MS,
119
+ });
120
+ trimActivityHeadlineCache();
121
+ return { headline: heuristic, source: "heuristic", model: null };
122
+ }
123
+ const controller = new AbortController();
124
+ const timeout = setTimeout(() => controller.abort(), ACTIVITY_HEADLINE_TIMEOUT_MS);
125
+ const model = process.env.ORGX_ACTIVITY_SUMMARY_MODEL?.trim() || DEFAULT_ACTIVITY_HEADLINE_MODEL;
126
+ const prompt = [
127
+ "Create one short activity title for a dashboard header.",
128
+ "Rules:",
129
+ "- Max 96 characters.",
130
+ "- Keep key numbers/status markers (for example: 15 tasks, 0 blocked).",
131
+ "- No markdown, no quotes, no trailing period unless needed.",
132
+ "- Prefer plain language over jargon.",
133
+ "",
134
+ `Type: ${normalizedType || "activity"}`,
135
+ normalizedTitle ? `Current title: ${normalizedTitle}` : "",
136
+ "Full detail:",
137
+ normalizedText,
138
+ ]
139
+ .filter(Boolean)
140
+ .join("\n");
141
+ try {
142
+ const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
143
+ method: "POST",
144
+ headers: {
145
+ "Content-Type": "application/json",
146
+ Authorization: `Bearer ${apiKey}`,
147
+ },
148
+ body: JSON.stringify({
149
+ model,
150
+ temperature: 0.1,
151
+ max_tokens: 48,
152
+ messages: [
153
+ {
154
+ role: "system",
155
+ content: "You write concise activity headers for operational dashboards. Return only the header text.",
156
+ },
157
+ {
158
+ role: "user",
159
+ content: prompt,
160
+ },
161
+ ],
162
+ }),
163
+ signal: controller.signal,
164
+ });
165
+ if (!response.ok) {
166
+ throw new Error(`headline model request failed (${response.status})`);
167
+ }
168
+ const payload = (await response.json());
169
+ const generated = cleanActivityHeadline(extractCompletionText(payload) ?? "");
170
+ const headline = generated || heuristic;
171
+ const source = generated ? "llm" : "heuristic";
172
+ activityHeadlineCache.set(cacheKey, {
173
+ headline,
174
+ source,
175
+ expiresAt: Date.now() + ACTIVITY_HEADLINE_CACHE_TTL_MS,
176
+ });
177
+ trimActivityHeadlineCache();
178
+ return { headline, source, model };
179
+ }
180
+ catch {
181
+ activityHeadlineCache.set(cacheKey, {
182
+ headline: heuristic,
183
+ source: "heuristic",
184
+ expiresAt: Date.now() + ACTIVITY_HEADLINE_CACHE_TTL_MS,
185
+ });
186
+ trimActivityHeadlineCache();
187
+ return { headline: heuristic, source: "heuristic", model: null };
188
+ }
189
+ finally {
190
+ clearTimeout(timeout);
191
+ }
192
+ }
@@ -0,0 +1,13 @@
1
+ import type { LiveActivityItem } from "../../types.js";
2
+ type ListActivityPageResult = {
3
+ activities: LiveActivityItem[];
4
+ nextCursor: string | null;
5
+ };
6
+ type BuildArtifactFallbackDeps = {
7
+ listActivityPage: (input: {
8
+ limit: number;
9
+ cursor: string | null;
10
+ }) => ListActivityPageResult;
11
+ };
12
+ export declare function createLocalArtifactDetailFallbackBuilder(deps: BuildArtifactFallbackDeps): (artifactId: string, warning: string) => Record<string, unknown> | null;
13
+ export {};