@simplr-ai/connect 0.7.5 → 0.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +236 -43
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -115,6 +115,84 @@ function mcpConfigurationPaths(homeDirectory, workingDirectory, organizationId)
|
|
|
115
115
|
];
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
// src/work-order-support.ts
|
|
119
|
+
function workOrderClassification(payload) {
|
|
120
|
+
return {
|
|
121
|
+
...typeof payload.work_order_kind === "string" ? { work_order_kind: payload.work_order_kind } : {},
|
|
122
|
+
...typeof payload.source_type === "string" ? { source_type: payload.source_type } : {},
|
|
123
|
+
...typeof payload.execution_mode === "string" ? { execution_mode: payload.execution_mode } : {}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function awaitsFeaturePlanApproval(classification) {
|
|
127
|
+
return classification.work_order_kind === "feature" && classification.source_type === "feedback" && classification.execution_mode === "plan";
|
|
128
|
+
}
|
|
129
|
+
function workOrderPayload(classification) {
|
|
130
|
+
return {
|
|
131
|
+
...classification.work_order_kind ? { work_order_kind: classification.work_order_kind } : {},
|
|
132
|
+
...classification.source_type ? { source_type: classification.source_type } : {},
|
|
133
|
+
...classification.execution_mode ? { execution_mode: classification.execution_mode } : {}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
async function postWithApiKey(url, body, apiKey) {
|
|
137
|
+
const response = await fetch(url, {
|
|
138
|
+
method: "POST",
|
|
139
|
+
headers: {
|
|
140
|
+
"Content-Type": "application/json",
|
|
141
|
+
"X-API-Key": apiKey
|
|
142
|
+
},
|
|
143
|
+
body: JSON.stringify(body),
|
|
144
|
+
signal: AbortSignal.timeout(1e4)
|
|
145
|
+
});
|
|
146
|
+
const payload = await response.json().catch(() => ({}));
|
|
147
|
+
if (!response.ok || !payload.content)
|
|
148
|
+
throw new Error(
|
|
149
|
+
payload.message || `Simplr request failed (${response.status})`
|
|
150
|
+
);
|
|
151
|
+
return payload.content;
|
|
152
|
+
}
|
|
153
|
+
function reservedSimplrConnectLane(organizationNamespace2) {
|
|
154
|
+
return `simplr-connect-reserved:${organizationNamespace2}`;
|
|
155
|
+
}
|
|
156
|
+
function isRunningTaskOwnedBy(task, expectedAssignee) {
|
|
157
|
+
return (task.task?.status || task.status) === "running" && (task.task?.assignee || task.assignee) === expectedAssignee;
|
|
158
|
+
}
|
|
159
|
+
var terminalCommentMarker = (action) => `[simplr-connect-terminal:${action}]`;
|
|
160
|
+
function taskStatus(task) {
|
|
161
|
+
return task.task?.status || task.status;
|
|
162
|
+
}
|
|
163
|
+
function taskBlockKind(task) {
|
|
164
|
+
const blockedEvent = [...task.events || []].reverse().find((event) => event.kind === "blocked");
|
|
165
|
+
return task.task?.block_kind || task.block_kind || blockedEvent?.payload?.kind;
|
|
166
|
+
}
|
|
167
|
+
function hasTerminalComment(task, action) {
|
|
168
|
+
const marker = terminalCommentMarker(action);
|
|
169
|
+
return (task.comments || []).some((comment) => comment.body?.includes(marker));
|
|
170
|
+
}
|
|
171
|
+
function reconcileTerminalBlock(action, detail, operations) {
|
|
172
|
+
const kind = action === "needs_input" ? "needs_input" : "transient";
|
|
173
|
+
const matches = (task) => taskStatus(task) === "blocked" && taskBlockKind(task) === kind && hasTerminalComment(task, action);
|
|
174
|
+
let current = operations.read();
|
|
175
|
+
if (!current)
|
|
176
|
+
throw new Error(`Hermes work-board ${action} preflight failed`);
|
|
177
|
+
if (matches(current)) return true;
|
|
178
|
+
if (!hasTerminalComment(current, action)) {
|
|
179
|
+
const body = `${detail}
|
|
180
|
+
|
|
181
|
+
${terminalCommentMarker(action)}`;
|
|
182
|
+
const commented = operations.comment(body);
|
|
183
|
+
current = operations.read();
|
|
184
|
+
if (!current || !commented && !hasTerminalComment(current, action))
|
|
185
|
+
throw new Error(`Hermes work-board ${action} comment reconciliation failed`);
|
|
186
|
+
}
|
|
187
|
+
if (matches(current)) return true;
|
|
188
|
+
const blocked = operations.block(kind);
|
|
189
|
+
current = operations.read();
|
|
190
|
+
if (current && matches(current)) return true;
|
|
191
|
+
if (!blocked)
|
|
192
|
+
throw new Error(`Hermes work-board ${action} reconciliation failed`);
|
|
193
|
+
throw new Error(`Hermes work-board ${action} verification failed`);
|
|
194
|
+
}
|
|
195
|
+
|
|
118
196
|
// src/index.ts
|
|
119
197
|
var AmbiguousHermesRunError = class extends Error {
|
|
120
198
|
};
|
|
@@ -122,7 +200,7 @@ var KanbanReconciliationError = class extends Error {
|
|
|
122
200
|
};
|
|
123
201
|
var DeferredUpdateError = class extends Error {
|
|
124
202
|
};
|
|
125
|
-
var APP_VERSION = "0.7.
|
|
203
|
+
var APP_VERSION = "0.7.6";
|
|
126
204
|
var STANDALONE_EXECUTABLE = process.env.SIMPLR_CONNECT_STANDALONE === "true";
|
|
127
205
|
var MANIFEST_PUBLIC_KEY = process.env.SIMPLR_CONNECT_MANIFEST_PUBLIC_KEY || "";
|
|
128
206
|
var HERMES_INSTALL_COMMIT = "542e146b055f0d43767ac43cdb9e78054b240263";
|
|
@@ -1583,11 +1661,47 @@ function isSimplrWorkOrder(command) {
|
|
|
1583
1661
|
String(command.payload.execution_mode)
|
|
1584
1662
|
);
|
|
1585
1663
|
}
|
|
1664
|
+
async function claimSimplrFeedbackInCloud(state, command) {
|
|
1665
|
+
if (String(command.payload.source_type) !== "feedback") return;
|
|
1666
|
+
const sourceId = String(command.payload.source_id || "");
|
|
1667
|
+
if (!sourceId) throw new Error("Simplr feedback source is missing");
|
|
1668
|
+
const agentToken = await ensureAgentCredential(state);
|
|
1669
|
+
await postWithApiKey(
|
|
1670
|
+
`${state.api_url}/v1/feedback/agent/items/${encodeURIComponent(sourceId)}/claim`,
|
|
1671
|
+
{},
|
|
1672
|
+
agentToken
|
|
1673
|
+
);
|
|
1674
|
+
}
|
|
1675
|
+
function readSimplrKanbanTask(executable, state, taskId) {
|
|
1676
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
1677
|
+
const shown = commandResult(
|
|
1678
|
+
executable,
|
|
1679
|
+
[
|
|
1680
|
+
...hermesProfileArgs(state),
|
|
1681
|
+
"kanban",
|
|
1682
|
+
"--board",
|
|
1683
|
+
`simplr-${organizationNamespace(state.organization_id)}`,
|
|
1684
|
+
"show",
|
|
1685
|
+
taskId,
|
|
1686
|
+
"--json"
|
|
1687
|
+
],
|
|
1688
|
+
1e4
|
|
1689
|
+
);
|
|
1690
|
+
if (shown.ok) return JSON.parse(shown.output);
|
|
1691
|
+
}
|
|
1692
|
+
return null;
|
|
1693
|
+
}
|
|
1694
|
+
function simplrKanbanTaskStatus(task) {
|
|
1695
|
+
return task.task?.status || task.status;
|
|
1696
|
+
}
|
|
1586
1697
|
function ensureSimplrKanbanTask(state, command, prompt) {
|
|
1587
1698
|
if (!isSimplrWorkOrder(command)) return void 0;
|
|
1588
1699
|
const executable = detectHermesExecutable();
|
|
1589
1700
|
if (!executable) throw new Error("Hermes executable is unavailable");
|
|
1590
1701
|
const boardSlug = `simplr-${organizationNamespace(state.organization_id)}`;
|
|
1702
|
+
const reservedAssignee = reservedSimplrConnectLane(
|
|
1703
|
+
organizationNamespace(state.organization_id)
|
|
1704
|
+
);
|
|
1591
1705
|
const boards = commandResult(
|
|
1592
1706
|
executable,
|
|
1593
1707
|
[...hermesProfileArgs(state), "kanban", "boards", "list", "--json"],
|
|
@@ -1664,10 +1778,12 @@ ${prompt}`;
|
|
|
1664
1778
|
`simplr-connect:${state.organization_id}:${command.id}`,
|
|
1665
1779
|
"--created-by",
|
|
1666
1780
|
"Simplr Connect",
|
|
1781
|
+
"--assignee",
|
|
1782
|
+
reservedAssignee,
|
|
1667
1783
|
"--skill",
|
|
1668
1784
|
"simplr-remote-operations",
|
|
1669
1785
|
"--initial-status",
|
|
1670
|
-
"
|
|
1786
|
+
"running",
|
|
1671
1787
|
"--json"
|
|
1672
1788
|
],
|
|
1673
1789
|
15e3
|
|
@@ -1676,12 +1792,33 @@ ${prompt}`;
|
|
|
1676
1792
|
throw new Error("Hermes could not persist the Simplr work order");
|
|
1677
1793
|
const task = JSON.parse(createdTask.output);
|
|
1678
1794
|
if (!task.id) throw new Error("Hermes did not return a work-order task id");
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1795
|
+
let current = readSimplrKanbanTask(executable, state, task.id);
|
|
1796
|
+
if (!current) throw new Error("Hermes could not verify the Simplr work-order task");
|
|
1797
|
+
if (simplrKanbanTaskStatus(current) === "ready") {
|
|
1798
|
+
const claimed = commandResult(
|
|
1799
|
+
executable,
|
|
1800
|
+
[
|
|
1801
|
+
...hermesProfileArgs(state),
|
|
1802
|
+
"kanban",
|
|
1803
|
+
"--board",
|
|
1804
|
+
boardSlug,
|
|
1805
|
+
"claim",
|
|
1806
|
+
task.id,
|
|
1807
|
+
"--ttl",
|
|
1808
|
+
"3600"
|
|
1809
|
+
],
|
|
1810
|
+
1e4
|
|
1811
|
+
);
|
|
1812
|
+
current = readSimplrKanbanTask(executable, state, task.id);
|
|
1813
|
+
if (!claimed.ok && simplrKanbanTaskStatus(current || {}) !== "running") {
|
|
1814
|
+
throw new Error("Hermes could not claim the Simplr work-order task");
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
if (!current || !isRunningTaskOwnedBy(current, reservedAssignee)) {
|
|
1818
|
+
throw new Error(
|
|
1819
|
+
"Hermes Simplr work-order task is not running in its reserved ownership lane"
|
|
1820
|
+
);
|
|
1821
|
+
}
|
|
1685
1822
|
return task.id;
|
|
1686
1823
|
}
|
|
1687
1824
|
function updateSimplrKanbanTask(state, taskId, action, detail) {
|
|
@@ -1710,7 +1847,47 @@ function updateSimplrKanbanTask(state, taskId, action, detail) {
|
|
|
1710
1847
|
1e4
|
|
1711
1848
|
).ok;
|
|
1712
1849
|
}
|
|
1713
|
-
|
|
1850
|
+
if (action === "failed" || action === "needs_input") {
|
|
1851
|
+
try {
|
|
1852
|
+
return reconcileTerminalBlock(action, detail.slice(0, 2e3), {
|
|
1853
|
+
read: () => readSimplrKanbanTask(executable, state, taskId),
|
|
1854
|
+
comment: (body) => commandResult(
|
|
1855
|
+
executable,
|
|
1856
|
+
[
|
|
1857
|
+
...hermesProfileArgs(state),
|
|
1858
|
+
"kanban",
|
|
1859
|
+
"--board",
|
|
1860
|
+
`simplr-${organizationNamespace(state.organization_id)}`,
|
|
1861
|
+
"comment",
|
|
1862
|
+
"--author",
|
|
1863
|
+
"Simplr Connect",
|
|
1864
|
+
taskId,
|
|
1865
|
+
body
|
|
1866
|
+
],
|
|
1867
|
+
1e4
|
|
1868
|
+
).ok,
|
|
1869
|
+
block: (kind) => commandResult(
|
|
1870
|
+
executable,
|
|
1871
|
+
[
|
|
1872
|
+
...hermesProfileArgs(state),
|
|
1873
|
+
"kanban",
|
|
1874
|
+
"--board",
|
|
1875
|
+
`simplr-${organizationNamespace(state.organization_id)}`,
|
|
1876
|
+
"block",
|
|
1877
|
+
"--kind",
|
|
1878
|
+
kind,
|
|
1879
|
+
taskId
|
|
1880
|
+
],
|
|
1881
|
+
1e4
|
|
1882
|
+
).ok
|
|
1883
|
+
});
|
|
1884
|
+
} catch (error) {
|
|
1885
|
+
throw new KanbanReconciliationError(
|
|
1886
|
+
error instanceof Error ? error.message : `Hermes work-board ${action} reconciliation failed`
|
|
1887
|
+
);
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
const args = [
|
|
1714
1891
|
...hermesProfileArgs(state),
|
|
1715
1892
|
"kanban",
|
|
1716
1893
|
"--board",
|
|
@@ -1724,39 +1901,21 @@ function updateSimplrKanbanTask(state, taskId, action, detail) {
|
|
|
1724
1901
|
source: "simplr-connect",
|
|
1725
1902
|
rules_version: "simplr-work-order-v1"
|
|
1726
1903
|
})
|
|
1727
|
-
] : [
|
|
1728
|
-
...hermesProfileArgs(state),
|
|
1729
|
-
"kanban",
|
|
1730
|
-
"--board",
|
|
1731
|
-
`simplr-${organizationNamespace(state.organization_id)}`,
|
|
1732
|
-
"block",
|
|
1733
|
-
"--kind",
|
|
1734
|
-
"transient",
|
|
1735
|
-
taskId,
|
|
1736
|
-
detail.slice(0, 2e3)
|
|
1737
1904
|
];
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
`simplr-${organizationNamespace(state.organization_id)}`,
|
|
1747
|
-
"show",
|
|
1748
|
-
taskId,
|
|
1749
|
-
"--json"
|
|
1750
|
-
],
|
|
1751
|
-
1e4
|
|
1905
|
+
const matchesAction = (current) => {
|
|
1906
|
+
const currentStatus = simplrKanbanTaskStatus(current);
|
|
1907
|
+
return action === "complete" && currentStatus === "done";
|
|
1908
|
+
};
|
|
1909
|
+
const initial = readSimplrKanbanTask(executable, state, taskId);
|
|
1910
|
+
if (!initial) {
|
|
1911
|
+
throw new KanbanReconciliationError(
|
|
1912
|
+
`Hermes work-board ${action} preflight failed`
|
|
1752
1913
|
);
|
|
1753
|
-
if (shown.ok) {
|
|
1754
|
-
const current = JSON.parse(shown.output);
|
|
1755
|
-
const currentStatus = current.task?.status || current.status;
|
|
1756
|
-
if (action === "complete" && currentStatus === "done" || action === "failed" && currentStatus === "blocked")
|
|
1757
|
-
return true;
|
|
1758
|
-
}
|
|
1759
1914
|
}
|
|
1915
|
+
if (matchesAction(initial)) return true;
|
|
1916
|
+
if (commandResult(executable, args, 1e4).ok) return true;
|
|
1917
|
+
const reconciled = readSimplrKanbanTask(executable, state, taskId);
|
|
1918
|
+
if (reconciled && matchesAction(reconciled)) return true;
|
|
1760
1919
|
throw new KanbanReconciliationError(
|
|
1761
1920
|
`Hermes work-board ${action} reconciliation failed`
|
|
1762
1921
|
);
|
|
@@ -2167,13 +2326,24 @@ async function monitorHermesRun(state, command, runId, startedAt) {
|
|
|
2167
2326
|
command_id: command.id,
|
|
2168
2327
|
type: command.type,
|
|
2169
2328
|
label: command.reason,
|
|
2329
|
+
...workOrderClassification(command.payload),
|
|
2170
2330
|
run_id: runId,
|
|
2171
2331
|
status: "completed",
|
|
2172
2332
|
result: output,
|
|
2173
2333
|
started_at: startedAt,
|
|
2174
2334
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2175
2335
|
});
|
|
2176
|
-
|
|
2336
|
+
const awaitingPlanApproval = awaitsFeaturePlanApproval(
|
|
2337
|
+
workOrderClassification(command.payload)
|
|
2338
|
+
);
|
|
2339
|
+
updateSimplrKanbanTask(
|
|
2340
|
+
state,
|
|
2341
|
+
taskId,
|
|
2342
|
+
awaitingPlanApproval ? "needs_input" : "complete",
|
|
2343
|
+
awaitingPlanApproval ? `Feature plan ready for comment and approval. Review the plan below, add guidance, then explicitly approve a new implementation work order.
|
|
2344
|
+
|
|
2345
|
+
${output}` : output
|
|
2346
|
+
);
|
|
2177
2347
|
return output;
|
|
2178
2348
|
}
|
|
2179
2349
|
if (status.status === "cancelled")
|
|
@@ -2186,6 +2356,7 @@ async function monitorHermesRun(state, command, runId, startedAt) {
|
|
|
2186
2356
|
command_id: command.id,
|
|
2187
2357
|
type: command.type,
|
|
2188
2358
|
label: command.reason,
|
|
2359
|
+
...workOrderClassification(command.payload),
|
|
2189
2360
|
run_id: runId,
|
|
2190
2361
|
status: run.status,
|
|
2191
2362
|
started_at: startedAt,
|
|
@@ -2197,6 +2368,7 @@ async function monitorHermesRun(state, command, runId, startedAt) {
|
|
|
2197
2368
|
command_id: command.id,
|
|
2198
2369
|
type: command.type,
|
|
2199
2370
|
label: command.reason,
|
|
2371
|
+
...workOrderClassification(command.payload),
|
|
2200
2372
|
run_id: runId,
|
|
2201
2373
|
status: run.status,
|
|
2202
2374
|
approval_command: approval.approval_command,
|
|
@@ -2254,11 +2426,14 @@ async function runHermesCommand(state, command) {
|
|
|
2254
2426
|
const journal = await loadCommandJournal();
|
|
2255
2427
|
const existing = journal[command.id];
|
|
2256
2428
|
if (existing?.status === "completed") {
|
|
2429
|
+
const awaitingPlanApproval = awaitsFeaturePlanApproval(existing);
|
|
2257
2430
|
updateSimplrKanbanTask(
|
|
2258
2431
|
state,
|
|
2259
2432
|
existing.kanban_task_id,
|
|
2260
|
-
"complete",
|
|
2261
|
-
|
|
2433
|
+
awaitingPlanApproval ? "needs_input" : "complete",
|
|
2434
|
+
awaitingPlanApproval ? `Feature plan ready for comment and approval. Review the plan below, add guidance, then explicitly approve a new implementation work order.
|
|
2435
|
+
|
|
2436
|
+
${existing.result || "Hermes completed the plan"}` : existing.result || "Hermes completed the task"
|
|
2262
2437
|
);
|
|
2263
2438
|
return "Hermes completed this task before Simplr reconnected";
|
|
2264
2439
|
}
|
|
@@ -2273,6 +2448,21 @@ async function runHermesCommand(state, command) {
|
|
|
2273
2448
|
if (!runId) {
|
|
2274
2449
|
const prompt = typeof command.payload.prompt === "string" ? command.payload.prompt : "";
|
|
2275
2450
|
if (!prompt) throw new Error("Hermes task prompt is missing");
|
|
2451
|
+
if (existing?.status !== "preparing") {
|
|
2452
|
+
await updateCommandJournal({
|
|
2453
|
+
command_id: command.id,
|
|
2454
|
+
organization_id: state.organization_id,
|
|
2455
|
+
organization_name: state.organization_name,
|
|
2456
|
+
workstation_id: state.workstation_id,
|
|
2457
|
+
type: command.type,
|
|
2458
|
+
label: command.reason,
|
|
2459
|
+
...workOrderClassification(command.payload),
|
|
2460
|
+
status: "preparing",
|
|
2461
|
+
started_at: startedAt,
|
|
2462
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2463
|
+
});
|
|
2464
|
+
}
|
|
2465
|
+
await claimSimplrFeedbackInCloud(state, command);
|
|
2276
2466
|
const kanbanTaskId = ensureSimplrKanbanTask(state, command, prompt);
|
|
2277
2467
|
await updateCommandJournal({
|
|
2278
2468
|
command_id: command.id,
|
|
@@ -2281,6 +2471,7 @@ async function runHermesCommand(state, command) {
|
|
|
2281
2471
|
workstation_id: state.workstation_id,
|
|
2282
2472
|
type: command.type,
|
|
2283
2473
|
label: command.reason,
|
|
2474
|
+
...workOrderClassification(command.payload),
|
|
2284
2475
|
status: "creating",
|
|
2285
2476
|
kanban_task_id: kanbanTaskId,
|
|
2286
2477
|
started_at: startedAt,
|
|
@@ -2303,6 +2494,7 @@ async function runHermesCommand(state, command) {
|
|
|
2303
2494
|
command_id: command.id,
|
|
2304
2495
|
type: command.type,
|
|
2305
2496
|
label: command.reason,
|
|
2497
|
+
...workOrderClassification(command.payload),
|
|
2306
2498
|
run_id: runId,
|
|
2307
2499
|
status: "running",
|
|
2308
2500
|
started_at: startedAt,
|
|
@@ -2612,6 +2804,7 @@ async function applyCommand(state, command, inventoryAlreadySynced) {
|
|
|
2612
2804
|
command_id: command.id,
|
|
2613
2805
|
type: command.type,
|
|
2614
2806
|
label: command.reason,
|
|
2807
|
+
...workOrderClassification(command.payload),
|
|
2615
2808
|
run_id: existing?.run_id,
|
|
2616
2809
|
status: "failed",
|
|
2617
2810
|
started_at: existing?.started_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -2667,7 +2860,7 @@ async function resumeHermesRuns(state) {
|
|
|
2667
2860
|
workstation_id: connection.workstation_id,
|
|
2668
2861
|
type: "run_hermes",
|
|
2669
2862
|
reason: entry.label,
|
|
2670
|
-
payload:
|
|
2863
|
+
payload: workOrderPayload(entry)
|
|
2671
2864
|
},
|
|
2672
2865
|
false
|
|
2673
2866
|
);
|