@useorgx/openclaw-plugin 0.4.6 → 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 (137) hide show
  1. package/README.md +310 -24
  2. package/dashboard/dist/assets/B5NEElEI.css +1 -0
  3. package/dashboard/dist/assets/BhapSNAs.js +215 -0
  4. package/dashboard/dist/assets/iFdvE7lx.js +1 -0
  5. package/dashboard/dist/assets/jRJsmpYM.js +1 -0
  6. package/dashboard/dist/index.html +2 -2
  7. package/dist/activity-actor-fields.d.ts +3 -0
  8. package/dist/activity-actor-fields.js +128 -0
  9. package/dist/activity-store.js +12 -19
  10. package/dist/agent-context-store.js +5 -25
  11. package/dist/agent-run-store.js +5 -25
  12. package/dist/agent-suite.js +1 -8
  13. package/dist/artifacts/register-artifact.d.ts +47 -0
  14. package/dist/artifacts/register-artifact.js +271 -0
  15. package/dist/auth/flows.d.ts +47 -0
  16. package/dist/auth/flows.js +169 -0
  17. package/dist/auth-store.js +14 -39
  18. package/dist/byok-store.js +5 -19
  19. package/dist/cli/orgx.d.ts +66 -0
  20. package/dist/cli/orgx.js +91 -0
  21. package/dist/config/refresh.d.ts +32 -0
  22. package/dist/config/refresh.js +55 -0
  23. package/dist/config/resolution.d.ts +37 -0
  24. package/dist/config/resolution.js +178 -0
  25. package/dist/contracts/client.d.ts +1 -0
  26. package/dist/contracts/client.js +7 -5
  27. package/dist/contracts/shared-types.d.ts +147 -0
  28. package/dist/contracts/shared-types.js +3 -0
  29. package/dist/contracts/types.d.ts +1 -130
  30. package/dist/contracts/types.js +5 -0
  31. package/dist/entities/auto-assignment.d.ts +36 -0
  32. package/dist/entities/auto-assignment.js +115 -0
  33. package/dist/entity-comment-store.js +5 -25
  34. package/dist/hash-utils.d.ts +2 -0
  35. package/dist/hash-utils.js +12 -0
  36. package/dist/http/helpers/activity-headline.d.ts +10 -0
  37. package/dist/http/helpers/activity-headline.js +192 -0
  38. package/dist/http/helpers/artifact-fallback.d.ts +13 -0
  39. package/dist/http/helpers/artifact-fallback.js +148 -0
  40. package/dist/http/helpers/auto-continue-engine.d.ts +298 -0
  41. package/dist/http/helpers/auto-continue-engine.js +1218 -0
  42. package/dist/http/helpers/autopilot-operations.d.ts +157 -0
  43. package/dist/http/helpers/autopilot-operations.js +403 -0
  44. package/dist/http/helpers/autopilot-runtime.d.ts +42 -0
  45. package/dist/http/helpers/autopilot-runtime.js +319 -0
  46. package/dist/http/helpers/autopilot-slice-utils.d.ts +38 -0
  47. package/dist/http/helpers/autopilot-slice-utils.js +476 -0
  48. package/dist/http/helpers/decision-mapper.d.ts +12 -0
  49. package/dist/http/helpers/decision-mapper.js +44 -0
  50. package/dist/http/helpers/dispatch-lifecycle.d.ts +102 -0
  51. package/dist/http/helpers/dispatch-lifecycle.js +604 -0
  52. package/dist/http/helpers/hash-utils.d.ts +1 -0
  53. package/dist/http/helpers/hash-utils.js +1 -0
  54. package/dist/http/helpers/kickoff-context.d.ts +12 -0
  55. package/dist/http/helpers/kickoff-context.js +154 -0
  56. package/dist/http/helpers/mission-control.d.ts +94 -0
  57. package/dist/http/helpers/mission-control.js +894 -0
  58. package/dist/http/helpers/openclaw-cli.d.ts +37 -0
  59. package/dist/http/helpers/openclaw-cli.js +283 -0
  60. package/dist/http/helpers/runtime-sse.d.ts +20 -0
  61. package/dist/http/helpers/runtime-sse.js +110 -0
  62. package/dist/http/helpers/value-utils.d.ts +6 -0
  63. package/dist/http/helpers/value-utils.js +67 -0
  64. package/dist/http/index.d.ts +88 -0
  65. package/dist/http/index.js +2353 -0
  66. package/dist/http/router.d.ts +23 -0
  67. package/dist/http/router.js +23 -0
  68. package/dist/http/routes/agent-control.d.ts +79 -0
  69. package/dist/http/routes/agent-control.js +684 -0
  70. package/dist/http/routes/agent-suite.d.ts +29 -0
  71. package/dist/http/routes/agent-suite.js +198 -0
  72. package/dist/http/routes/agents-catalog.d.ts +40 -0
  73. package/dist/http/routes/agents-catalog.js +83 -0
  74. package/dist/http/routes/billing.d.ts +23 -0
  75. package/dist/http/routes/billing.js +55 -0
  76. package/dist/http/routes/debug.d.ts +14 -0
  77. package/dist/http/routes/debug.js +21 -0
  78. package/dist/http/routes/decision-actions.d.ts +13 -0
  79. package/dist/http/routes/decision-actions.js +66 -0
  80. package/dist/http/routes/delegation.d.ts +19 -0
  81. package/dist/http/routes/delegation.js +32 -0
  82. package/dist/http/routes/entities.d.ts +47 -0
  83. package/dist/http/routes/entities.js +152 -0
  84. package/dist/http/routes/entity-dynamic.d.ts +25 -0
  85. package/dist/http/routes/entity-dynamic.js +191 -0
  86. package/dist/http/routes/health.d.ts +22 -0
  87. package/dist/http/routes/health.js +49 -0
  88. package/dist/http/routes/live-legacy.d.ts +110 -0
  89. package/dist/http/routes/live-legacy.js +598 -0
  90. package/dist/http/routes/live-misc.d.ts +69 -0
  91. package/dist/http/routes/live-misc.js +206 -0
  92. package/dist/http/routes/live-snapshot.d.ts +90 -0
  93. package/dist/http/routes/live-snapshot.js +297 -0
  94. package/dist/http/routes/mission-control-actions.d.ts +83 -0
  95. package/dist/http/routes/mission-control-actions.js +541 -0
  96. package/dist/http/routes/mission-control-read.d.ts +28 -0
  97. package/dist/http/routes/mission-control-read.js +67 -0
  98. package/dist/http/routes/onboarding.d.ts +34 -0
  99. package/dist/http/routes/onboarding.js +101 -0
  100. package/dist/http/routes/run-control.d.ts +24 -0
  101. package/dist/http/routes/run-control.js +86 -0
  102. package/dist/http/routes/runtime-hooks.d.ts +69 -0
  103. package/dist/http/routes/runtime-hooks.js +437 -0
  104. package/dist/http/routes/settings-byok.d.ts +23 -0
  105. package/dist/http/routes/settings-byok.js +163 -0
  106. package/dist/http/routes/summary.d.ts +18 -0
  107. package/dist/http/routes/summary.js +42 -0
  108. package/dist/http/routes/work-artifacts.d.ts +9 -0
  109. package/dist/http/routes/work-artifacts.js +36 -0
  110. package/dist/http/shared-state.d.ts +16 -0
  111. package/dist/http/shared-state.js +1 -0
  112. package/dist/http-handler.d.ts +1 -88
  113. package/dist/http-handler.js +1 -9664
  114. package/dist/index.js +122 -2121
  115. package/dist/json-utils.d.ts +1 -0
  116. package/dist/json-utils.js +8 -0
  117. package/dist/local-openclaw.js +8 -0
  118. package/dist/mcp-client-setup.js +75 -90
  119. package/dist/next-up-queue-store.js +4 -18
  120. package/dist/runtime-instance-store.js +8 -34
  121. package/dist/services/background.d.ts +23 -0
  122. package/dist/services/background.js +23 -0
  123. package/dist/services/instrumentation.d.ts +29 -0
  124. package/dist/services/instrumentation.js +136 -0
  125. package/dist/snapshot-store.js +5 -25
  126. package/dist/stores/json-store.d.ts +11 -0
  127. package/dist/stores/json-store.js +42 -0
  128. package/dist/sync/outbox-replay.d.ts +55 -0
  129. package/dist/sync/outbox-replay.js +514 -0
  130. package/dist/tools/core-tools.d.ts +76 -0
  131. package/dist/tools/core-tools.js +1005 -0
  132. package/dist/worker-supervisor.js +15 -0
  133. package/package.json +6 -1
  134. package/dashboard/dist/assets/0tOC3wSN.js +0 -214
  135. package/dashboard/dist/assets/Bm8QnMJ_.js +0 -1
  136. package/dashboard/dist/assets/CyxZio4Y.js +0 -1
  137. package/dashboard/dist/assets/DaAIOik3.css +0 -1
@@ -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 {};
@@ -0,0 +1,148 @@
1
+ function asRecord(value) {
2
+ if (!value || typeof value !== "object" || Array.isArray(value))
3
+ return null;
4
+ return value;
5
+ }
6
+ function flattenActivityMetadata(value) {
7
+ const record = asRecord(value);
8
+ if (!record)
9
+ return null;
10
+ const nested = asRecord(record.metadata);
11
+ if (!nested)
12
+ return record;
13
+ return { ...record, ...nested };
14
+ }
15
+ function metadataString(metadata, keys) {
16
+ if (!metadata)
17
+ return null;
18
+ for (const key of keys) {
19
+ const value = metadata[key];
20
+ if (typeof value === "string" && value.trim().length > 0) {
21
+ return value.trim();
22
+ }
23
+ }
24
+ return null;
25
+ }
26
+ function metadataNumber(metadata, keys) {
27
+ if (!metadata)
28
+ return null;
29
+ for (const key of keys) {
30
+ const value = metadata[key];
31
+ if (typeof value === "number" && Number.isFinite(value))
32
+ return value;
33
+ if (typeof value === "string" && value.trim().length > 0) {
34
+ const parsed = Number(value.trim());
35
+ if (Number.isFinite(parsed))
36
+ return parsed;
37
+ }
38
+ }
39
+ return null;
40
+ }
41
+ function resolveArtifactIdFromActivityItem(item) {
42
+ const metadata = flattenActivityMetadata(item.metadata);
43
+ if (!metadata)
44
+ return null;
45
+ const rawId = metadataString(metadata, ["artifact_id", "artifactId", "work_artifact_id"]);
46
+ return rawId && rawId.length > 0 ? rawId : null;
47
+ }
48
+ function resolveArtifactHref(value) {
49
+ if (!value)
50
+ return null;
51
+ const trimmed = value.trim();
52
+ if (!trimmed)
53
+ return null;
54
+ if (/^https?:\/\//i.test(trimmed))
55
+ return trimmed;
56
+ return `/orgx/api/live/filesystem/open?path=${encodeURIComponent(trimmed)}`;
57
+ }
58
+ export function createLocalArtifactDetailFallbackBuilder(deps) {
59
+ return (artifactId, warning) => {
60
+ let cursor = null;
61
+ const maxPages = 8;
62
+ for (let pageIndex = 0; pageIndex < maxPages; pageIndex += 1) {
63
+ const page = deps.listActivityPage({ limit: 500, cursor });
64
+ const activities = Array.isArray(page.activities) ? page.activities : [];
65
+ for (const item of activities) {
66
+ const matchId = resolveArtifactIdFromActivityItem(item);
67
+ if (!matchId || matchId !== artifactId)
68
+ continue;
69
+ const metadata = flattenActivityMetadata(item.metadata) ?? {};
70
+ const rawPath = metadataString(metadata, [
71
+ "url",
72
+ "path",
73
+ "file_path",
74
+ "filepath",
75
+ "artifact_path",
76
+ "output_path",
77
+ "external_url",
78
+ "artifact_url",
79
+ ]) ?? null;
80
+ const artifactHref = resolveArtifactHref(rawPath);
81
+ const artifactType = metadataString(metadata, ["artifact_type", "artifactType", "type"]) ?? "report";
82
+ const artifactName = metadataString(metadata, ["artifact_name", "artifactName", "name", "title"]) ??
83
+ item.title?.trim() ??
84
+ `Artifact ${artifactId.slice(0, 8)}`;
85
+ const status = metadataString(metadata, ["artifact_status", "status", "state"]) ??
86
+ (typeof metadata.event === "string" && metadata.event.includes("buffered")
87
+ ? "buffered"
88
+ : "draft");
89
+ const entityType = metadataString(metadata, ["entity_type", "entityType"]) ??
90
+ (metadataString(metadata, ["initiative_id", "initiativeId"])
91
+ ? "initiative"
92
+ : "task");
93
+ const entityId = metadataString(metadata, [
94
+ "entity_id",
95
+ "entityId",
96
+ "task_id",
97
+ "taskId",
98
+ "workstream_id",
99
+ "workstreamId",
100
+ "initiative_id",
101
+ "initiativeId",
102
+ "run_id",
103
+ "runId",
104
+ ]) ?? "local";
105
+ const description = metadataString(metadata, ["description", "summary", "message"]) ??
106
+ item.summary ??
107
+ item.description ??
108
+ null;
109
+ const version = Math.max(1, Math.round(metadataNumber(metadata, ["version", "artifact_version"]) ?? 1));
110
+ const createdAt = item.timestamp ?? new Date().toISOString();
111
+ const updatedAt = item.timestamp ?? createdAt;
112
+ return {
113
+ artifact: {
114
+ id: artifactId,
115
+ name: artifactName,
116
+ description,
117
+ artifact_url: artifactHref ?? "",
118
+ artifact_type: artifactType,
119
+ status,
120
+ version,
121
+ entity_type: entityType,
122
+ entity_id: entityId,
123
+ metadata: {
124
+ ...metadata,
125
+ local_fallback: true,
126
+ local_warning: warning,
127
+ local_activity_event_id: item.id,
128
+ local_activity_type: item.type,
129
+ local_activity_timestamp: item.timestamp,
130
+ local_source_path: rawPath,
131
+ },
132
+ created_at: createdAt,
133
+ updated_at: updatedAt,
134
+ catalog: null,
135
+ cached_metadata: null,
136
+ },
137
+ relationships: [],
138
+ localFallback: true,
139
+ warning,
140
+ };
141
+ }
142
+ if (!page.nextCursor)
143
+ break;
144
+ cursor = page.nextCursor;
145
+ }
146
+ return null;
147
+ };
148
+ }