happy-imou-cloud 2.1.28 → 2.1.30
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/{BaseReasoningProcessor-DYVFvY85.mjs → BaseReasoningProcessor-C1iacoJW.mjs} +2 -2
- package/dist/{BaseReasoningProcessor-AsSwxX2U.cjs → BaseReasoningProcessor-Ch9R4qmn.cjs} +2 -2
- package/dist/{ProviderSelectionHandler-Cd_vN8wA.mjs → ProviderSelectionHandler-2siFKlgs.mjs} +2 -2
- package/dist/{ProviderSelectionHandler-BKfo21qx.cjs → ProviderSelectionHandler-Dx6x0Nd4.cjs} +2 -2
- package/dist/{api-DYS9sGJr.mjs → api-C68U-kRs.mjs} +626 -126
- package/dist/{api-ChV_1TP7.cjs → api-DK1gyZAZ.cjs} +626 -126
- package/dist/{command-ErrXrwTw.cjs → command-Cb9nikZh.cjs} +2 -2
- package/dist/{command-Bx9UY5D5.mjs → command-D32x08k9.mjs} +2 -2
- package/dist/{index-CDyeCS7U.cjs → index-BLeiCte-.cjs} +174 -32
- package/dist/{index-DtlrIihs.mjs → index-DQ76ZTNL.mjs} +171 -29
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/lib.cjs +1 -1
- package/dist/lib.d.cts +100 -0
- package/dist/lib.d.mts +100 -0
- package/dist/lib.mjs +1 -1
- package/dist/{registerKillSessionHandler-IlzXqONk.mjs → registerKillSessionHandler-CadrzRgP.mjs} +16 -6
- package/dist/{registerKillSessionHandler-CBuJR_D8.cjs → registerKillSessionHandler-DD9uUk4w.cjs} +16 -6
- package/dist/{runClaude-VlpAUO9C.mjs → runClaude-CkY6XYJa.mjs} +4 -4
- package/dist/{runClaude-D_YCDVmM.cjs → runClaude-U9sxsnU-.cjs} +4 -4
- package/dist/{runCodex-jisfibyM.cjs → runCodex-Beikmv-L.cjs} +134 -9
- package/dist/{runCodex-CL1yQHxl.mjs → runCodex-C6kV0jfX.mjs} +134 -9
- package/dist/{runGemini-BtBrzhkF.cjs → runGemini-BTyqf5MR.cjs} +4 -4
- package/dist/{runGemini-EPv-yxY4.mjs → runGemini-IEzJdhc-.mjs} +4 -4
- package/package.json +1 -1
- package/scripts/release-smoke.mjs +3 -0
|
@@ -18,7 +18,7 @@ import { spawn } from 'node:child_process';
|
|
|
18
18
|
import { Expo } from 'expo-server-sdk';
|
|
19
19
|
|
|
20
20
|
var name = "happy-imou-cloud";
|
|
21
|
-
var version = "2.1.
|
|
21
|
+
var version = "2.1.30";
|
|
22
22
|
var description = "hicloud - Imou 企业定制版。关键是 happy!移动端远程 AI 编程工具,支持 Claude Code、Codex 和 Gemini CLI";
|
|
23
23
|
var author = "long.zhu";
|
|
24
24
|
var license = "MIT";
|
|
@@ -1210,7 +1210,9 @@ const HappyOrgTaskContextSchema = z$1.object({
|
|
|
1210
1210
|
taskId: z$1.string().min(1),
|
|
1211
1211
|
organizationId: z$1.string().min(1),
|
|
1212
1212
|
memberAgentId: z$1.string().min(1),
|
|
1213
|
-
supervisorAgentId: z$1.string().min(1)
|
|
1213
|
+
supervisorAgentId: z$1.string().min(1),
|
|
1214
|
+
positionId: z$1.string().min(1).optional().nullable(),
|
|
1215
|
+
responsibilityId: z$1.string().min(1).optional().nullable()
|
|
1214
1216
|
});
|
|
1215
1217
|
const HappyOrgTaskControlSchema = z$1.object({
|
|
1216
1218
|
action: z$1.enum(["terminate", "reopen"]),
|
|
@@ -1222,7 +1224,9 @@ const HappyOrgTaskControlSchema = z$1.object({
|
|
|
1222
1224
|
const HappyOrgReplyContextSchema = z$1.object({
|
|
1223
1225
|
dispatchId: z$1.string().min(1),
|
|
1224
1226
|
scope: z$1.string().min(1),
|
|
1225
|
-
replyTo: z$1.string().min(1)
|
|
1227
|
+
replyTo: z$1.string().min(1),
|
|
1228
|
+
positionId: z$1.string().min(1).optional().nullable(),
|
|
1229
|
+
responsibilityId: z$1.string().min(1).optional().nullable()
|
|
1226
1230
|
});
|
|
1227
1231
|
const HappyOrgDispatchBusinessAckStatusSchema = z$1.enum([
|
|
1228
1232
|
"accepted",
|
|
@@ -1575,6 +1579,15 @@ const AgentMessageSchema = z$1.object({
|
|
|
1575
1579
|
const MessageContentSchema = z$1.union([UserMessageSchema, AgentMessageSchema]);
|
|
1576
1580
|
|
|
1577
1581
|
const HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION = "1.7.3-local-repo-v1";
|
|
1582
|
+
const HAPPY_ORG_WRITER_LOCK_SCHEMA_VERSION = "1.7.3-writer-lock-v1";
|
|
1583
|
+
const WRITER_LOCK_LEASE_DURATION_SECONDS = 900;
|
|
1584
|
+
const WRITER_LOCK_RENEW_BEFORE_SECONDS = 300;
|
|
1585
|
+
const WRITER_LOCK_CEO_TAKEOVER_AFTER_SECONDS = 1800;
|
|
1586
|
+
const WRITER_LOCK_RENEW_INTERVAL_MS = Math.max(
|
|
1587
|
+
(WRITER_LOCK_LEASE_DURATION_SECONDS - WRITER_LOCK_RENEW_BEFORE_SECONDS) * 1e3,
|
|
1588
|
+
1e3
|
|
1589
|
+
);
|
|
1590
|
+
let writerLockSequence = 0;
|
|
1578
1591
|
const TaskStatusSchema = z$1.enum([
|
|
1579
1592
|
"draft",
|
|
1580
1593
|
"ready",
|
|
@@ -1658,10 +1671,46 @@ const UpdateTaskRequestSchema = BaseRequestSchema.extend({
|
|
|
1658
1671
|
expectedRevision: z$1.number().nullable(),
|
|
1659
1672
|
nextState: TaskUpdatePayloadSchema
|
|
1660
1673
|
});
|
|
1674
|
+
const AgentUpsertPayloadSchema = z$1.object({
|
|
1675
|
+
id: z$1.string().min(1),
|
|
1676
|
+
slug: z$1.string().min(1),
|
|
1677
|
+
role: z$1.string().min(1),
|
|
1678
|
+
name: z$1.string().min(1),
|
|
1679
|
+
sessionId: z$1.string().min(1),
|
|
1680
|
+
sessionTitle: z$1.string().min(1),
|
|
1681
|
+
runtimePath: z$1.string().nullable(),
|
|
1682
|
+
workingDirectoryRelativePath: z$1.string().nullable().optional(),
|
|
1683
|
+
machineId: z$1.string().nullable(),
|
|
1684
|
+
backend: z$1.string().nullable(),
|
|
1685
|
+
supervisorAgentId: z$1.string().nullable(),
|
|
1686
|
+
createdAt: z$1.number(),
|
|
1687
|
+
updatedAt: z$1.number()
|
|
1688
|
+
});
|
|
1689
|
+
const PositionUpsertPayloadSchema = z$1.object({
|
|
1690
|
+
positionId: z$1.string().min(1),
|
|
1691
|
+
slug: z$1.string().min(1),
|
|
1692
|
+
label: z$1.string().min(1),
|
|
1693
|
+
role: z$1.string().nullable(),
|
|
1694
|
+
workingDirectoryRelativePath: z$1.string().nullable(),
|
|
1695
|
+
agentId: z$1.string().nullable(),
|
|
1696
|
+
agentName: z$1.string().nullable(),
|
|
1697
|
+
supervisorAgentId: z$1.string().nullable(),
|
|
1698
|
+
responsibilityIds: z$1.array(z$1.string()),
|
|
1699
|
+
occupancyStatus: z$1.enum(["occupied", "vacancy", "assigned", "active"]),
|
|
1700
|
+
createdAt: z$1.number(),
|
|
1701
|
+
updatedAt: z$1.number()
|
|
1702
|
+
});
|
|
1703
|
+
const UpsertAgentRequestSchema = BaseRequestSchema.extend({
|
|
1704
|
+
requestType: z$1.literal("upsert_agent"),
|
|
1705
|
+
agent: AgentUpsertPayloadSchema,
|
|
1706
|
+
position: PositionUpsertPayloadSchema,
|
|
1707
|
+
expectedRevision: z$1.number().nullable()
|
|
1708
|
+
});
|
|
1661
1709
|
const RequestSchema = z$1.discriminatedUnion("requestType", [
|
|
1662
1710
|
ReplaceGoalRequestSchema,
|
|
1663
1711
|
CreateTaskRequestSchema,
|
|
1664
|
-
UpdateTaskRequestSchema
|
|
1712
|
+
UpdateTaskRequestSchema,
|
|
1713
|
+
UpsertAgentRequestSchema
|
|
1665
1714
|
]);
|
|
1666
1715
|
const RevisionEnvelopeSchema = (value) => z$1.object({
|
|
1667
1716
|
schemaVersion: z$1.literal(HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION),
|
|
@@ -1674,16 +1723,23 @@ const PositionValueSchema = z$1.object({
|
|
|
1674
1723
|
positionId: z$1.string(),
|
|
1675
1724
|
slug: z$1.string(),
|
|
1676
1725
|
label: z$1.string(),
|
|
1677
|
-
role: z$1.string(),
|
|
1678
|
-
|
|
1679
|
-
|
|
1726
|
+
role: z$1.string().nullable().optional(),
|
|
1727
|
+
workingDirectoryRelativePath: z$1.string().nullable().optional(),
|
|
1728
|
+
agentId: z$1.string().nullable().optional(),
|
|
1729
|
+
agentName: z$1.string().nullable().optional(),
|
|
1680
1730
|
supervisorAgentId: z$1.string().nullable(),
|
|
1681
1731
|
responsibilityIds: z$1.array(z$1.string()),
|
|
1682
|
-
occupancyStatus: z$1.enum(["occupied", "vacancy"]),
|
|
1732
|
+
occupancyStatus: z$1.enum(["occupied", "vacancy", "assigned", "active"]),
|
|
1683
1733
|
truthPath: z$1.string(),
|
|
1684
1734
|
createdAt: z$1.number(),
|
|
1685
1735
|
updatedAt: z$1.number()
|
|
1686
1736
|
});
|
|
1737
|
+
const WorkspaceValueSchema = z$1.object({
|
|
1738
|
+
positionId: z$1.string(),
|
|
1739
|
+
slug: z$1.string(),
|
|
1740
|
+
relativePath: z$1.string().nullable(),
|
|
1741
|
+
updatedAt: z$1.number()
|
|
1742
|
+
});
|
|
1687
1743
|
const ResponsibilityValueSchema = z$1.object({
|
|
1688
1744
|
responsibilityId: z$1.string(),
|
|
1689
1745
|
title: z$1.string(),
|
|
@@ -1693,6 +1749,7 @@ const ResponsibilityValueSchema = z$1.object({
|
|
|
1693
1749
|
status: z$1.enum(["vacancy", "assigned", "in_progress"]),
|
|
1694
1750
|
memberAgentId: z$1.string().nullable(),
|
|
1695
1751
|
memberName: z$1.string().nullable(),
|
|
1752
|
+
role: z$1.string().nullable().optional(),
|
|
1696
1753
|
activeTaskId: z$1.string().nullable(),
|
|
1697
1754
|
createdAt: z$1.number(),
|
|
1698
1755
|
updatedAt: z$1.number()
|
|
@@ -1775,11 +1832,45 @@ const LogValueSchema = z$1.object({
|
|
|
1775
1832
|
});
|
|
1776
1833
|
const GoalEnvelopeSchema = RevisionEnvelopeSchema(GoalSchema);
|
|
1777
1834
|
const PositionEnvelopeSchema = RevisionEnvelopeSchema(PositionValueSchema);
|
|
1835
|
+
const WorkspaceEnvelopeSchema = RevisionEnvelopeSchema(WorkspaceValueSchema);
|
|
1778
1836
|
const ResponsibilityEnvelopeSchema = RevisionEnvelopeSchema(z$1.array(ResponsibilityValueSchema));
|
|
1779
1837
|
const TaskEnvelopeSchema = RevisionEnvelopeSchema(TaskValueSchema);
|
|
1780
1838
|
const WorkflowEnvelopeSchema = RevisionEnvelopeSchema(WorkflowValueSchema);
|
|
1781
1839
|
const ResultEnvelopeSchema = RevisionEnvelopeSchema(ResultValueSchema);
|
|
1782
1840
|
const LogEnvelopeSchema = RevisionEnvelopeSchema(LogValueSchema);
|
|
1841
|
+
const WriterLockRecordSchema = z$1.object({
|
|
1842
|
+
schemaVersion: z$1.literal(HAPPY_ORG_WRITER_LOCK_SCHEMA_VERSION),
|
|
1843
|
+
lockId: z$1.string().min(1),
|
|
1844
|
+
target: z$1.string().min(1),
|
|
1845
|
+
holderType: z$1.literal("cli-writer"),
|
|
1846
|
+
holderId: z$1.string().min(1),
|
|
1847
|
+
requestId: z$1.string().min(1),
|
|
1848
|
+
pid: z$1.number().int().nonnegative(),
|
|
1849
|
+
acquiredAt: z$1.number().int().nonnegative(),
|
|
1850
|
+
renewedAt: z$1.number().int().nonnegative(),
|
|
1851
|
+
expiresAt: z$1.number().int().nonnegative(),
|
|
1852
|
+
leaseDurationSeconds: z$1.literal(WRITER_LOCK_LEASE_DURATION_SECONDS),
|
|
1853
|
+
renewBeforeSeconds: z$1.literal(WRITER_LOCK_RENEW_BEFORE_SECONDS),
|
|
1854
|
+
ceoTakeoverAfterSeconds: z$1.literal(WRITER_LOCK_CEO_TAKEOVER_AFTER_SECONDS),
|
|
1855
|
+
takeoverOf: z$1.object({
|
|
1856
|
+
lockId: z$1.string().min(1),
|
|
1857
|
+
holderId: z$1.string().min(1),
|
|
1858
|
+
requestId: z$1.string().min(1),
|
|
1859
|
+
renewedAt: z$1.number().int().nonnegative()
|
|
1860
|
+
}).nullable()
|
|
1861
|
+
});
|
|
1862
|
+
class WriterLockConflictError extends Error {
|
|
1863
|
+
state;
|
|
1864
|
+
target;
|
|
1865
|
+
existing;
|
|
1866
|
+
constructor(state, target, message, existing = null) {
|
|
1867
|
+
super(message);
|
|
1868
|
+
this.name = "WriterLockConflictError";
|
|
1869
|
+
this.state = state;
|
|
1870
|
+
this.target = target;
|
|
1871
|
+
this.existing = existing;
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1783
1874
|
function normalizePortablePath(value) {
|
|
1784
1875
|
const trimmed = value?.trim();
|
|
1785
1876
|
if (!trimmed) {
|
|
@@ -1787,14 +1878,37 @@ function normalizePortablePath(value) {
|
|
|
1787
1878
|
}
|
|
1788
1879
|
return trimmed.replace(/\\/g, "/").replace(/\/{2,}/g, "/").replace(/\/$/, "");
|
|
1789
1880
|
}
|
|
1881
|
+
function normalizeRelativePath(value) {
|
|
1882
|
+
const segments = (value ?? "").split(/[\\/]+/).map((segment) => segment.trim()).filter(Boolean).filter((segment) => segment !== "." && segment !== "..");
|
|
1883
|
+
return segments.length > 0 ? segments.join("/") : null;
|
|
1884
|
+
}
|
|
1885
|
+
function resolveRelativePath(rootPath, relativePath) {
|
|
1886
|
+
const normalizedRelativePath = normalizeRelativePath(relativePath);
|
|
1887
|
+
if (!normalizedRelativePath) {
|
|
1888
|
+
return null;
|
|
1889
|
+
}
|
|
1890
|
+
return path.join(rootPath, ...normalizedRelativePath.split("/"));
|
|
1891
|
+
}
|
|
1892
|
+
function normalizeOccupancyStatus(value) {
|
|
1893
|
+
switch (value) {
|
|
1894
|
+
case "vacancy":
|
|
1895
|
+
return "vacancy";
|
|
1896
|
+
case "active":
|
|
1897
|
+
return "active";
|
|
1898
|
+
case "assigned":
|
|
1899
|
+
case "occupied":
|
|
1900
|
+
default:
|
|
1901
|
+
return "assigned";
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1790
1904
|
function normalizeAdviceRoute(value) {
|
|
1791
1905
|
return value === "gm" || value === "lead" || value === "user" ? value : null;
|
|
1792
1906
|
}
|
|
1793
1907
|
function buildResponsibilityId(slug) {
|
|
1794
1908
|
return `responsibility-${slug}`;
|
|
1795
1909
|
}
|
|
1796
|
-
function
|
|
1797
|
-
return
|
|
1910
|
+
function buildPositionRoot(rootPath, slug) {
|
|
1911
|
+
return path.join(rootPath, "positions", slug);
|
|
1798
1912
|
}
|
|
1799
1913
|
function toTaskSlug(taskId, title) {
|
|
1800
1914
|
const slug = title.trim().toLowerCase().replace(/[^a-z0-9\u4e00-\u9fa5]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -1839,6 +1953,74 @@ function buildRequestFilePath(rootPath, requestId) {
|
|
|
1839
1953
|
function buildTaskRoot(rootPath, taskId) {
|
|
1840
1954
|
return path.join(rootPath, "tasks", taskId);
|
|
1841
1955
|
}
|
|
1956
|
+
function nextWriterLockId() {
|
|
1957
|
+
writerLockSequence += 1;
|
|
1958
|
+
return `writer-lock-${process.pid}-${Date.now().toString(36)}-${writerLockSequence.toString(36)}`;
|
|
1959
|
+
}
|
|
1960
|
+
function normalizeWriterLockTarget(target) {
|
|
1961
|
+
return target.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "writer-lock";
|
|
1962
|
+
}
|
|
1963
|
+
function buildWriterLockPath(rootPath, target) {
|
|
1964
|
+
return path.join(rootPath, ".runtime", "locks", `${normalizeWriterLockTarget(target)}.lock.json`);
|
|
1965
|
+
}
|
|
1966
|
+
function buildWriterLockRecord(params) {
|
|
1967
|
+
const now = params.now ?? Date.now();
|
|
1968
|
+
return {
|
|
1969
|
+
schemaVersion: HAPPY_ORG_WRITER_LOCK_SCHEMA_VERSION,
|
|
1970
|
+
lockId: nextWriterLockId(),
|
|
1971
|
+
target: params.target,
|
|
1972
|
+
holderType: "cli-writer",
|
|
1973
|
+
holderId: params.holderId,
|
|
1974
|
+
requestId: params.requestId,
|
|
1975
|
+
pid: process.pid,
|
|
1976
|
+
acquiredAt: now,
|
|
1977
|
+
renewedAt: now,
|
|
1978
|
+
expiresAt: now + WRITER_LOCK_LEASE_DURATION_SECONDS * 1e3,
|
|
1979
|
+
leaseDurationSeconds: WRITER_LOCK_LEASE_DURATION_SECONDS,
|
|
1980
|
+
renewBeforeSeconds: WRITER_LOCK_RENEW_BEFORE_SECONDS,
|
|
1981
|
+
ceoTakeoverAfterSeconds: WRITER_LOCK_CEO_TAKEOVER_AFTER_SECONDS,
|
|
1982
|
+
takeoverOf: params.takeoverOf ? {
|
|
1983
|
+
lockId: params.takeoverOf.lockId,
|
|
1984
|
+
holderId: params.takeoverOf.holderId,
|
|
1985
|
+
requestId: params.takeoverOf.requestId,
|
|
1986
|
+
renewedAt: params.takeoverOf.renewedAt
|
|
1987
|
+
} : null
|
|
1988
|
+
};
|
|
1989
|
+
}
|
|
1990
|
+
function getWriterLockAgeMs(lock, now) {
|
|
1991
|
+
return Math.max(now - Math.max(lock.renewedAt, lock.acquiredAt), 0);
|
|
1992
|
+
}
|
|
1993
|
+
async function removeWriterLockFile(lockPath) {
|
|
1994
|
+
await unlink(lockPath).catch(async () => {
|
|
1995
|
+
await rm(lockPath, { force: true });
|
|
1996
|
+
});
|
|
1997
|
+
}
|
|
1998
|
+
async function readWriterLockRecord(lockPath) {
|
|
1999
|
+
const payload = await readJson(lockPath);
|
|
2000
|
+
const parsed = WriterLockRecordSchema.safeParse(payload);
|
|
2001
|
+
return parsed.success ? parsed.data : null;
|
|
2002
|
+
}
|
|
2003
|
+
function isFileExistsError(error) {
|
|
2004
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
|
|
2005
|
+
}
|
|
2006
|
+
function isFileNotFoundError(error) {
|
|
2007
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
2008
|
+
}
|
|
2009
|
+
function getWriterLockTargetsForRequest(request) {
|
|
2010
|
+
if (request.requestType === "replace_goal") {
|
|
2011
|
+
return ["goal"];
|
|
2012
|
+
}
|
|
2013
|
+
if (request.requestType === "upsert_agent") {
|
|
2014
|
+
return [`position-${request.position.positionId}`, "responsibilities"];
|
|
2015
|
+
}
|
|
2016
|
+
if (request.requestType === "create_task") {
|
|
2017
|
+
return ["task-create", "responsibilities"];
|
|
2018
|
+
}
|
|
2019
|
+
return [`task-${request.taskId}`, "responsibilities"];
|
|
2020
|
+
}
|
|
2021
|
+
function getWriterLockTargetsForTurnReport(taskId) {
|
|
2022
|
+
return [`task-${taskId}`, "responsibilities"];
|
|
2023
|
+
}
|
|
1842
2024
|
async function loadTaskEnvelope(rootPath, taskId) {
|
|
1843
2025
|
const taskRoot = buildTaskRoot(rootPath, taskId);
|
|
1844
2026
|
const task = await readEnvelope(path.join(taskRoot, "TASK.json"), TaskEnvelopeSchema);
|
|
@@ -1863,15 +2045,52 @@ async function nextTaskId(rootPath) {
|
|
|
1863
2045
|
const nextNumber = taskIds.map((taskId) => Number.parseInt(taskId.replace(/^T-/, ""), 10)).filter((value) => Number.isFinite(value)).reduce((max, value) => Math.max(max, value), 0) + 1;
|
|
1864
2046
|
return `T-${String(nextNumber).padStart(3, "0")}`;
|
|
1865
2047
|
}
|
|
1866
|
-
async function
|
|
2048
|
+
async function listPositionEntries(rootPath) {
|
|
1867
2049
|
try {
|
|
1868
2050
|
const entries = await readdir(path.join(rootPath, "positions"), { withFileTypes: true });
|
|
1869
|
-
const
|
|
1870
|
-
|
|
2051
|
+
const positionEntries = await Promise.all(entries.map(async (entry) => {
|
|
2052
|
+
if (entry.isDirectory()) {
|
|
2053
|
+
const positionRoot = path.join(rootPath, "positions", entry.name);
|
|
2054
|
+
const position = await readEnvelope(path.join(positionRoot, "POSITION.json"), PositionEnvelopeSchema);
|
|
2055
|
+
if (!position) {
|
|
2056
|
+
return null;
|
|
2057
|
+
}
|
|
2058
|
+
const workspace = await readEnvelope(path.join(positionRoot, "WORKSPACE.json"), WorkspaceEnvelopeSchema);
|
|
2059
|
+
return {
|
|
2060
|
+
position,
|
|
2061
|
+
workspace,
|
|
2062
|
+
source: "directory"
|
|
2063
|
+
};
|
|
2064
|
+
}
|
|
2065
|
+
if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
2066
|
+
const position = await readEnvelope(path.join(rootPath, "positions", entry.name), PositionEnvelopeSchema);
|
|
2067
|
+
if (!position) {
|
|
2068
|
+
return null;
|
|
2069
|
+
}
|
|
2070
|
+
return {
|
|
2071
|
+
position,
|
|
2072
|
+
workspace: null,
|
|
2073
|
+
source: "legacy-file"
|
|
2074
|
+
};
|
|
2075
|
+
}
|
|
2076
|
+
return null;
|
|
2077
|
+
}));
|
|
2078
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
2079
|
+
for (const entry of positionEntries.filter((value) => value !== null)) {
|
|
2080
|
+
const key = entry.position.value.positionId;
|
|
2081
|
+
const existing = deduped.get(key) ?? null;
|
|
2082
|
+
if (!existing || existing.source === "legacy-file" && entry.source === "directory") {
|
|
2083
|
+
deduped.set(key, entry);
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
return [...deduped.values()];
|
|
1871
2087
|
} catch {
|
|
1872
2088
|
return [];
|
|
1873
2089
|
}
|
|
1874
2090
|
}
|
|
2091
|
+
async function listPositionEnvelopes(rootPath) {
|
|
2092
|
+
return (await listPositionEntries(rootPath)).map((entry) => entry.position);
|
|
2093
|
+
}
|
|
1875
2094
|
async function readResponsibilities(rootPath) {
|
|
1876
2095
|
return await readEnvelope(path.join(rootPath, "RESPONSIBILITIES.json"), ResponsibilityEnvelopeSchema);
|
|
1877
2096
|
}
|
|
@@ -1904,16 +2123,18 @@ async function rebuildResponsibilities(rootPath) {
|
|
|
1904
2123
|
const existing = await readResponsibilities(rootPath);
|
|
1905
2124
|
const previousRevision = existing?.revision ?? 0;
|
|
1906
2125
|
const responsibilities = positions.map((position) => {
|
|
1907
|
-
const
|
|
2126
|
+
const responsibilityId = position.value.responsibilityIds[0] ?? buildResponsibilityId(position.value.slug);
|
|
2127
|
+
const activeTask = normalizedTasks.filter((task) => task.status !== "done" && task.status !== "terminated").filter((task) => task.responsibilityId === responsibilityId || (position.value.agentId ? task.ownerAgentId === position.value.agentId : false)).sort((left, right) => right.updatedAt - left.updatedAt)[0] ?? null;
|
|
1908
2128
|
return {
|
|
1909
|
-
responsibilityId
|
|
1910
|
-
title: `${position.value.
|
|
2129
|
+
responsibilityId,
|
|
2130
|
+
title: position.value.label || position.value.agentName || `${position.value.slug} responsibility`,
|
|
1911
2131
|
summary: activeTask?.summary ?? null,
|
|
1912
|
-
positionId:
|
|
1913
|
-
participantType: "agent",
|
|
1914
|
-
status: activeTask ? "in_progress" : "assigned",
|
|
1915
|
-
memberAgentId: position.value.agentId,
|
|
1916
|
-
memberName: position.value.agentName,
|
|
2132
|
+
positionId: position.value.positionId,
|
|
2133
|
+
participantType: position.value.agentId ? "agent" : "vacancy",
|
|
2134
|
+
status: position.value.agentId ? activeTask ? "in_progress" : "assigned" : "vacancy",
|
|
2135
|
+
memberAgentId: position.value.agentId ?? null,
|
|
2136
|
+
memberName: position.value.agentName ?? null,
|
|
2137
|
+
role: position.value.role ?? null,
|
|
1917
2138
|
activeTaskId: activeTask?.taskId ?? null,
|
|
1918
2139
|
createdAt: position.value.createdAt,
|
|
1919
2140
|
updatedAt: Math.max(position.value.updatedAt, activeTask?.updatedAt ?? 0)
|
|
@@ -1984,12 +2205,104 @@ async function applyReplaceGoalRequest(rootPath, request) {
|
|
|
1984
2205
|
message: "goal updated"
|
|
1985
2206
|
};
|
|
1986
2207
|
}
|
|
2208
|
+
async function applyUpsertAgentRequest(rootPath, request) {
|
|
2209
|
+
const positionEntries = await listPositionEntries(rootPath);
|
|
2210
|
+
const existingPosition = positionEntries.find((entry) => entry.position.value.positionId === request.position.positionId || entry.position.value.slug === request.position.slug || entry.position.value.agentId === request.agent.id) ?? null;
|
|
2211
|
+
if (request.expectedRevision !== null && existingPosition?.position.revision !== request.expectedRevision) {
|
|
2212
|
+
const message = existingPosition ? `Position revision mismatch: expected ${request.expectedRevision}, got ${existingPosition.position.revision}` : `Position ${request.position.positionId} not found for expected revision ${request.expectedRevision}`;
|
|
2213
|
+
await updateRequestRecord(rootPath, request, {
|
|
2214
|
+
requestState: "recovery_required",
|
|
2215
|
+
processedAt: Date.now(),
|
|
2216
|
+
resultMessage: message
|
|
2217
|
+
});
|
|
2218
|
+
return {
|
|
2219
|
+
outcome: "recovery_required",
|
|
2220
|
+
requestId: request.requestId,
|
|
2221
|
+
taskId: null,
|
|
2222
|
+
message
|
|
2223
|
+
};
|
|
2224
|
+
}
|
|
2225
|
+
const positionRoot = buildPositionRoot(rootPath, request.position.slug);
|
|
2226
|
+
const now = Date.now();
|
|
2227
|
+
const positionValue = {
|
|
2228
|
+
positionId: request.position.positionId,
|
|
2229
|
+
slug: request.position.slug,
|
|
2230
|
+
label: request.position.label,
|
|
2231
|
+
role: request.position.role ?? request.agent.role,
|
|
2232
|
+
workingDirectoryRelativePath: normalizeRelativePath(
|
|
2233
|
+
request.position.workingDirectoryRelativePath ?? request.agent.workingDirectoryRelativePath
|
|
2234
|
+
),
|
|
2235
|
+
agentId: request.position.agentId ?? request.agent.id,
|
|
2236
|
+
agentName: request.position.agentName ?? request.agent.name,
|
|
2237
|
+
supervisorAgentId: request.position.supervisorAgentId ?? request.agent.supervisorAgentId,
|
|
2238
|
+
responsibilityIds: request.position.responsibilityIds.length > 0 ? request.position.responsibilityIds : [buildResponsibilityId(request.position.slug)],
|
|
2239
|
+
occupancyStatus: normalizeOccupancyStatus(request.position.occupancyStatus),
|
|
2240
|
+
truthPath: `positions/${request.position.slug}/POSITION.json`,
|
|
2241
|
+
createdAt: request.position.createdAt,
|
|
2242
|
+
updatedAt: request.agent.updatedAt
|
|
2243
|
+
};
|
|
2244
|
+
const existingWorkspace = existingPosition?.workspace ?? null;
|
|
2245
|
+
const workspaceValue = {
|
|
2246
|
+
positionId: request.position.positionId,
|
|
2247
|
+
slug: request.position.slug,
|
|
2248
|
+
relativePath: normalizeRelativePath(
|
|
2249
|
+
request.position.workingDirectoryRelativePath ?? request.agent.workingDirectoryRelativePath
|
|
2250
|
+
),
|
|
2251
|
+
updatedAt: request.agent.updatedAt
|
|
2252
|
+
};
|
|
2253
|
+
const runtimeValue = {
|
|
2254
|
+
agentId: request.agent.id,
|
|
2255
|
+
slug: request.agent.slug,
|
|
2256
|
+
role: request.agent.role,
|
|
2257
|
+
sessionId: request.agent.sessionId,
|
|
2258
|
+
sessionTitle: request.agent.sessionTitle,
|
|
2259
|
+
runtimePath: request.agent.runtimePath,
|
|
2260
|
+
assignedWorkingDirectoryRelativePath: workspaceValue.relativePath,
|
|
2261
|
+
assignedWorkingDirectoryPath: resolveRelativePath(rootPath, workspaceValue.relativePath),
|
|
2262
|
+
machineId: request.agent.machineId,
|
|
2263
|
+
backend: request.agent.backend,
|
|
2264
|
+
updatedAt: request.agent.updatedAt
|
|
2265
|
+
};
|
|
2266
|
+
await ensureDir(positionRoot);
|
|
2267
|
+
await writeEnvelope(path.join(positionRoot, "POSITION.json"), {
|
|
2268
|
+
schemaVersion: HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION,
|
|
2269
|
+
revision: existingPosition && JSON.stringify(existingPosition.position.value) === JSON.stringify(positionValue) ? existingPosition.position.revision : (existingPosition?.position.revision ?? 0) + 1,
|
|
2270
|
+
updatedAt: now,
|
|
2271
|
+
updatedBy: request.agent.id,
|
|
2272
|
+
value: positionValue
|
|
2273
|
+
});
|
|
2274
|
+
await writeEnvelope(path.join(positionRoot, "WORKSPACE.json"), {
|
|
2275
|
+
schemaVersion: HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION,
|
|
2276
|
+
revision: existingWorkspace && JSON.stringify(existingWorkspace.value) === JSON.stringify(workspaceValue) ? existingWorkspace.revision : (existingWorkspace?.revision ?? 0) + 1,
|
|
2277
|
+
updatedAt: now,
|
|
2278
|
+
updatedBy: request.agent.id,
|
|
2279
|
+
value: workspaceValue
|
|
2280
|
+
});
|
|
2281
|
+
await writeJson(path.join(rootPath, ".runtime", "agents", `${request.agent.slug}.json`), runtimeValue);
|
|
2282
|
+
if (existingPosition?.source === "legacy-file") {
|
|
2283
|
+
await rm(path.join(rootPath, "positions", `${existingPosition.position.value.positionId}.json`), { force: true });
|
|
2284
|
+
}
|
|
2285
|
+
await rebuildResponsibilities(rootPath);
|
|
2286
|
+
await updateRequestRecord(rootPath, request, {
|
|
2287
|
+
requestState: "applied",
|
|
2288
|
+
processedAt: now,
|
|
2289
|
+
resultMessage: `upserted ${request.agent.id}`
|
|
2290
|
+
});
|
|
2291
|
+
return {
|
|
2292
|
+
outcome: "applied",
|
|
2293
|
+
requestId: request.requestId,
|
|
2294
|
+
taskId: null,
|
|
2295
|
+
message: `upserted ${request.agent.id}`
|
|
2296
|
+
};
|
|
2297
|
+
}
|
|
1987
2298
|
async function applyCreateTaskRequest(rootPath, request) {
|
|
1988
2299
|
const taskId = await nextTaskId(rootPath);
|
|
1989
2300
|
const taskRoot = buildTaskRoot(rootPath, taskId);
|
|
1990
2301
|
const createdAt = Date.now();
|
|
1991
2302
|
const title = request.title.trim();
|
|
1992
2303
|
const position = (await listPositionEnvelopes(rootPath)).find((entry) => entry.value.agentId === request.ownerAgentId) ?? null;
|
|
2304
|
+
const responsibilityId = position?.value.responsibilityIds[0] ?? (position ? buildResponsibilityId(position.value.slug) : null);
|
|
2305
|
+
const responsibilityLabel = position?.value.label ?? `${request.ownerName} responsibility`;
|
|
1993
2306
|
const taskValue = {
|
|
1994
2307
|
taskId,
|
|
1995
2308
|
slug: toTaskSlug(taskId, title),
|
|
@@ -1997,8 +2310,8 @@ async function applyCreateTaskRequest(rootPath, request) {
|
|
|
1997
2310
|
ownerAgentId: request.ownerAgentId,
|
|
1998
2311
|
ownerName: request.ownerName,
|
|
1999
2312
|
ownerSessionId: request.ownerSessionId,
|
|
2000
|
-
responsibilityId
|
|
2001
|
-
responsibilityLabel
|
|
2313
|
+
responsibilityId,
|
|
2314
|
+
responsibilityLabel,
|
|
2002
2315
|
activeOwnerAgentId: null,
|
|
2003
2316
|
hasActiveOwner: false,
|
|
2004
2317
|
path: request.path,
|
|
@@ -2216,19 +2529,127 @@ async function applyUpdateTaskRequest(rootPath, request) {
|
|
|
2216
2529
|
message: `updated ${request.taskId}`
|
|
2217
2530
|
};
|
|
2218
2531
|
}
|
|
2219
|
-
async function
|
|
2220
|
-
const
|
|
2532
|
+
async function renewSingleWriterLock(lockPath, lockId) {
|
|
2533
|
+
const existing = await readWriterLockRecord(lockPath);
|
|
2534
|
+
if (!existing || existing.lockId !== lockId) {
|
|
2535
|
+
return;
|
|
2536
|
+
}
|
|
2537
|
+
const now = Date.now();
|
|
2538
|
+
await writeJson(lockPath, {
|
|
2539
|
+
...existing,
|
|
2540
|
+
renewedAt: now,
|
|
2541
|
+
expiresAt: now + WRITER_LOCK_LEASE_DURATION_SECONDS * 1e3
|
|
2542
|
+
});
|
|
2543
|
+
}
|
|
2544
|
+
async function acquireSingleWriterLock(params) {
|
|
2545
|
+
const lockPath = buildWriterLockPath(params.rootPath, params.target);
|
|
2221
2546
|
await ensureDir(path.dirname(lockPath));
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2547
|
+
let takeoverOf = null;
|
|
2548
|
+
while (true) {
|
|
2549
|
+
const now = Date.now();
|
|
2550
|
+
const record = buildWriterLockRecord({
|
|
2551
|
+
target: params.target,
|
|
2552
|
+
holderId: params.holderId,
|
|
2553
|
+
requestId: params.requestId,
|
|
2554
|
+
takeoverOf,
|
|
2555
|
+
now
|
|
2231
2556
|
});
|
|
2557
|
+
try {
|
|
2558
|
+
const handle = await open(lockPath, "wx");
|
|
2559
|
+
await handle.writeFile(JSON.stringify(record, null, 2), "utf8");
|
|
2560
|
+
await handle.close();
|
|
2561
|
+
const renewTimer = setInterval(() => {
|
|
2562
|
+
void renewSingleWriterLock(lockPath, record.lockId);
|
|
2563
|
+
}, WRITER_LOCK_RENEW_INTERVAL_MS);
|
|
2564
|
+
renewTimer.unref?.();
|
|
2565
|
+
return async () => {
|
|
2566
|
+
clearInterval(renewTimer);
|
|
2567
|
+
const existing = await readWriterLockRecord(lockPath);
|
|
2568
|
+
if (!existing || existing.lockId !== record.lockId) {
|
|
2569
|
+
return;
|
|
2570
|
+
}
|
|
2571
|
+
await removeWriterLockFile(lockPath);
|
|
2572
|
+
};
|
|
2573
|
+
} catch (error) {
|
|
2574
|
+
if (!isFileExistsError(error)) {
|
|
2575
|
+
throw error;
|
|
2576
|
+
}
|
|
2577
|
+
const existing = await readWriterLockRecord(lockPath);
|
|
2578
|
+
if (!existing) {
|
|
2579
|
+
try {
|
|
2580
|
+
const info = await stat(lockPath);
|
|
2581
|
+
const ageMs2 = Math.max(now - info.mtimeMs, 0);
|
|
2582
|
+
if (ageMs2 >= WRITER_LOCK_CEO_TAKEOVER_AFTER_SECONDS * 1e3) {
|
|
2583
|
+
await removeWriterLockFile(lockPath);
|
|
2584
|
+
takeoverOf = null;
|
|
2585
|
+
continue;
|
|
2586
|
+
}
|
|
2587
|
+
} catch (statError) {
|
|
2588
|
+
if (isFileNotFoundError(statError)) {
|
|
2589
|
+
takeoverOf = null;
|
|
2590
|
+
continue;
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
throw new WriterLockConflictError(
|
|
2594
|
+
"invalid",
|
|
2595
|
+
params.target,
|
|
2596
|
+
`Lock ${params.target} exists but is unreadable; mark recovery before retrying.`
|
|
2597
|
+
);
|
|
2598
|
+
}
|
|
2599
|
+
const ageMs = getWriterLockAgeMs(existing, now);
|
|
2600
|
+
if (ageMs >= WRITER_LOCK_CEO_TAKEOVER_AFTER_SECONDS * 1e3) {
|
|
2601
|
+
await removeWriterLockFile(lockPath);
|
|
2602
|
+
takeoverOf = existing;
|
|
2603
|
+
continue;
|
|
2604
|
+
}
|
|
2605
|
+
if (ageMs >= WRITER_LOCK_LEASE_DURATION_SECONDS * 1e3) {
|
|
2606
|
+
throw new WriterLockConflictError(
|
|
2607
|
+
"stale",
|
|
2608
|
+
params.target,
|
|
2609
|
+
`Lock ${params.target} is stale for ${Math.floor(ageMs / 1e3)}s and requires CEO recovery before takeover.`,
|
|
2610
|
+
existing
|
|
2611
|
+
);
|
|
2612
|
+
}
|
|
2613
|
+
throw new WriterLockConflictError(
|
|
2614
|
+
"active",
|
|
2615
|
+
params.target,
|
|
2616
|
+
`Lock ${params.target} is still held by ${existing.holderId} for request ${existing.requestId}.`,
|
|
2617
|
+
existing
|
|
2618
|
+
);
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
async function acquireWriterLocks(params) {
|
|
2623
|
+
const holderId = `cli-writer:${process.pid}`;
|
|
2624
|
+
const releaseSteps = [];
|
|
2625
|
+
const orderedTargets = [...new Set(params.targets)].sort();
|
|
2626
|
+
try {
|
|
2627
|
+
for (const target of orderedTargets) {
|
|
2628
|
+
releaseSteps.push(await acquireSingleWriterLock({
|
|
2629
|
+
rootPath: params.rootPath,
|
|
2630
|
+
target,
|
|
2631
|
+
holderId,
|
|
2632
|
+
requestId: params.requestId
|
|
2633
|
+
}));
|
|
2634
|
+
}
|
|
2635
|
+
} catch (error) {
|
|
2636
|
+
while (releaseSteps.length > 0) {
|
|
2637
|
+
const release = releaseSteps.pop();
|
|
2638
|
+
if (!release) {
|
|
2639
|
+
continue;
|
|
2640
|
+
}
|
|
2641
|
+
await release();
|
|
2642
|
+
}
|
|
2643
|
+
throw error;
|
|
2644
|
+
}
|
|
2645
|
+
return async () => {
|
|
2646
|
+
while (releaseSteps.length > 0) {
|
|
2647
|
+
const release = releaseSteps.pop();
|
|
2648
|
+
if (!release) {
|
|
2649
|
+
continue;
|
|
2650
|
+
}
|
|
2651
|
+
await release();
|
|
2652
|
+
}
|
|
2232
2653
|
};
|
|
2233
2654
|
}
|
|
2234
2655
|
async function processSingleRequest(rootPath, request) {
|
|
@@ -2241,11 +2662,38 @@ async function processSingleRequest(rootPath, request) {
|
|
|
2241
2662
|
message: request.resultMessage
|
|
2242
2663
|
};
|
|
2243
2664
|
}
|
|
2244
|
-
|
|
2665
|
+
let lockConflictMessage = null;
|
|
2666
|
+
const release = await acquireWriterLocks({
|
|
2667
|
+
rootPath,
|
|
2668
|
+
targets: getWriterLockTargetsForRequest(request),
|
|
2669
|
+
requestId: request.requestId
|
|
2670
|
+
}).catch(async (error) => {
|
|
2671
|
+
if (error instanceof WriterLockConflictError) {
|
|
2672
|
+
lockConflictMessage = error.message;
|
|
2673
|
+
await updateRequestRecord(rootPath, request, {
|
|
2674
|
+
requestState: "recovery_required",
|
|
2675
|
+
processedAt: Date.now(),
|
|
2676
|
+
resultMessage: error.message
|
|
2677
|
+
});
|
|
2678
|
+
return null;
|
|
2679
|
+
}
|
|
2680
|
+
throw error;
|
|
2681
|
+
});
|
|
2682
|
+
if (!release) {
|
|
2683
|
+
return {
|
|
2684
|
+
outcome: "recovery_required",
|
|
2685
|
+
requestId: request.requestId,
|
|
2686
|
+
taskId: "taskId" in request ? request.taskId : null,
|
|
2687
|
+
message: lockConflictMessage
|
|
2688
|
+
};
|
|
2689
|
+
}
|
|
2245
2690
|
try {
|
|
2246
2691
|
if (request.requestType === "replace_goal") {
|
|
2247
2692
|
return await applyReplaceGoalRequest(rootPath, request);
|
|
2248
2693
|
}
|
|
2694
|
+
if (request.requestType === "upsert_agent") {
|
|
2695
|
+
return await applyUpsertAgentRequest(rootPath, request);
|
|
2696
|
+
}
|
|
2249
2697
|
if (request.requestType === "create_task") {
|
|
2250
2698
|
return await applyCreateTaskRequest(rootPath, request);
|
|
2251
2699
|
}
|
|
@@ -2286,99 +2734,125 @@ function nextStatusFromTurnReport(report) {
|
|
|
2286
2734
|
return "active";
|
|
2287
2735
|
}
|
|
2288
2736
|
async function recordHappyOrgTurnReport(options) {
|
|
2289
|
-
const
|
|
2290
|
-
|
|
2737
|
+
const requestId = `turn-report:${options.report.taskId}`;
|
|
2738
|
+
let lockConflictMessage = null;
|
|
2739
|
+
const release = await acquireWriterLocks({
|
|
2740
|
+
rootPath: options.rootPath,
|
|
2741
|
+
targets: getWriterLockTargetsForTurnReport(options.report.taskId),
|
|
2742
|
+
requestId
|
|
2743
|
+
}).catch((error) => {
|
|
2744
|
+
if (error instanceof WriterLockConflictError) {
|
|
2745
|
+
lockConflictMessage = error.message;
|
|
2746
|
+
return null;
|
|
2747
|
+
}
|
|
2748
|
+
throw error;
|
|
2749
|
+
});
|
|
2750
|
+
if (!release) {
|
|
2291
2751
|
return {
|
|
2292
2752
|
outcome: "recovery_required",
|
|
2293
|
-
requestId
|
|
2753
|
+
requestId,
|
|
2294
2754
|
taskId: options.report.taskId,
|
|
2295
|
-
message: `
|
|
2755
|
+
message: lockConflictMessage ?? `Turn report blocked by writer lock for ${options.report.taskId}`
|
|
2296
2756
|
};
|
|
2297
2757
|
}
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
portablePath: normalizePortablePath(options.report.targetArtifact ?? bundle.task.value.path)
|
|
2313
|
-
};
|
|
2314
|
-
await writeEnvelope(path.join(buildTaskRoot(options.rootPath, options.report.taskId), "TASK.json"), {
|
|
2315
|
-
...bundle.task,
|
|
2316
|
-
revision: bundle.task.revision + 1,
|
|
2317
|
-
updatedAt: now,
|
|
2318
|
-
updatedBy: options.report.memberAgentId,
|
|
2319
|
-
value: nextTaskValue
|
|
2320
|
-
});
|
|
2321
|
-
await writeEnvelope(path.join(buildTaskRoot(options.rootPath, options.report.taskId), "WORKFLOW.json"), {
|
|
2322
|
-
schemaVersion: HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION,
|
|
2323
|
-
revision: (bundle.workflow?.revision ?? 0) + 1,
|
|
2324
|
-
updatedAt: now,
|
|
2325
|
-
updatedBy: options.report.memberAgentId,
|
|
2326
|
-
value: {
|
|
2327
|
-
taskId: options.report.taskId,
|
|
2758
|
+
try {
|
|
2759
|
+
const bundle = await loadTaskEnvelope(options.rootPath, options.report.taskId);
|
|
2760
|
+
if (!bundle) {
|
|
2761
|
+
return {
|
|
2762
|
+
outcome: "recovery_required",
|
|
2763
|
+
requestId,
|
|
2764
|
+
taskId: options.report.taskId,
|
|
2765
|
+
message: `Task ${options.report.taskId} not found`
|
|
2766
|
+
};
|
|
2767
|
+
}
|
|
2768
|
+
const now = Date.now();
|
|
2769
|
+
const nextStatus = nextStatusFromTurnReport(options.report);
|
|
2770
|
+
const nextTaskValue = {
|
|
2771
|
+
...bundle.task.value,
|
|
2328
2772
|
status: nextStatus,
|
|
2329
2773
|
summary: options.report.summary,
|
|
2330
|
-
|
|
2774
|
+
resultSummary: options.report.turnStatus === "task_complete" ? options.report.summary : bundle.task.value.resultSummary,
|
|
2775
|
+
blocker: nextStatus === "blocked" ? options.report.summary || options.report.blockerCode : null,
|
|
2331
2776
|
decisionNeeded: nextStatus === "waiting_decision" ? options.report.decisionNeeded ?? options.report.summary : null,
|
|
2332
|
-
adviceRoute: null,
|
|
2333
|
-
adviceSummary: null,
|
|
2334
|
-
lastRequestId: null,
|
|
2335
2777
|
updatedAt: now,
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
resultSummary: options.report.turnStatus === "task_complete" ? options.report.summary : bundle.result?.value.resultSummary ?? bundle.task.value.resultSummary,
|
|
2347
|
-
reviewSummary: bundle.result?.value.reviewSummary ?? null,
|
|
2348
|
-
assetCaptureStatus: bundle.result?.value.assetCaptureStatus ?? null,
|
|
2349
|
-
assetCaptureSummary: bundle.result?.value.assetCaptureSummary ?? null,
|
|
2350
|
-
assetReference: options.report.targetArtifact ?? bundle.result?.value.assetReference ?? null,
|
|
2351
|
-
closedAt: nextStatus === "waiting_close" ? now : bundle.result?.value.closedAt ?? null,
|
|
2778
|
+
lastUpdatedBy: options.report.memberAgentId,
|
|
2779
|
+
activeOwnerAgentId: options.report.memberAgentId,
|
|
2780
|
+
hasActiveOwner: nextStatus === "active",
|
|
2781
|
+
path: options.report.targetArtifact ?? bundle.task.value.path,
|
|
2782
|
+
portablePath: normalizePortablePath(options.report.targetArtifact ?? bundle.task.value.path)
|
|
2783
|
+
};
|
|
2784
|
+
const taskRoot = buildTaskRoot(options.rootPath, options.report.taskId);
|
|
2785
|
+
await writeEnvelope(path.join(taskRoot, "TASK.json"), {
|
|
2786
|
+
...bundle.task,
|
|
2787
|
+
revision: bundle.task.revision + 1,
|
|
2352
2788
|
updatedAt: now,
|
|
2353
|
-
updatedBy: options.report.memberAgentId
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2789
|
+
updatedBy: options.report.memberAgentId,
|
|
2790
|
+
value: nextTaskValue
|
|
2791
|
+
});
|
|
2792
|
+
await writeEnvelope(path.join(taskRoot, "WORKFLOW.json"), {
|
|
2793
|
+
schemaVersion: HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION,
|
|
2794
|
+
revision: (bundle.workflow?.revision ?? 0) + 1,
|
|
2795
|
+
updatedAt: now,
|
|
2796
|
+
updatedBy: options.report.memberAgentId,
|
|
2797
|
+
value: {
|
|
2798
|
+
taskId: options.report.taskId,
|
|
2799
|
+
status: nextStatus,
|
|
2800
|
+
summary: options.report.summary,
|
|
2801
|
+
blocker: nextStatus === "blocked" ? options.report.blockerCode ?? options.report.summary : null,
|
|
2802
|
+
decisionNeeded: nextStatus === "waiting_decision" ? options.report.decisionNeeded ?? options.report.summary : null,
|
|
2803
|
+
adviceRoute: null,
|
|
2804
|
+
adviceSummary: null,
|
|
2805
|
+
lastRequestId: null,
|
|
2806
|
+
updatedAt: now,
|
|
2807
|
+
updatedBy: options.report.memberAgentId
|
|
2808
|
+
}
|
|
2809
|
+
});
|
|
2810
|
+
await writeEnvelope(path.join(taskRoot, "RESULT.json"), {
|
|
2811
|
+
schemaVersion: HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION,
|
|
2812
|
+
revision: (bundle.result?.revision ?? 0) + 1,
|
|
2813
|
+
updatedAt: now,
|
|
2814
|
+
updatedBy: options.report.memberAgentId,
|
|
2815
|
+
value: {
|
|
2816
|
+
taskId: options.report.taskId,
|
|
2817
|
+
resultSummary: options.report.turnStatus === "task_complete" ? options.report.summary : bundle.result?.value.resultSummary ?? bundle.task.value.resultSummary,
|
|
2818
|
+
reviewSummary: bundle.result?.value.reviewSummary ?? null,
|
|
2819
|
+
assetCaptureStatus: bundle.result?.value.assetCaptureStatus ?? null,
|
|
2820
|
+
assetCaptureSummary: bundle.result?.value.assetCaptureSummary ?? null,
|
|
2821
|
+
assetReference: options.report.targetArtifact ?? bundle.result?.value.assetReference ?? null,
|
|
2822
|
+
closedAt: nextStatus === "waiting_close" ? now : bundle.result?.value.closedAt ?? null,
|
|
2823
|
+
updatedAt: now,
|
|
2824
|
+
updatedBy: options.report.memberAgentId
|
|
2825
|
+
}
|
|
2826
|
+
});
|
|
2827
|
+
await writeEnvelope(path.join(taskRoot, "LOG.json"), {
|
|
2828
|
+
schemaVersion: HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION,
|
|
2829
|
+
revision: (bundle.log?.revision ?? 0) + 1,
|
|
2830
|
+
updatedAt: now,
|
|
2831
|
+
updatedBy: options.report.memberAgentId,
|
|
2832
|
+
value: {
|
|
2833
|
+
taskId: options.report.taskId,
|
|
2834
|
+
entries: [
|
|
2835
|
+
...bundle.log?.value.entries ?? [],
|
|
2836
|
+
buildTaskLogEntry(
|
|
2837
|
+
"turn_report",
|
|
2838
|
+
`Recorded ${options.report.turnStatus} from ${options.report.memberAgentId}`,
|
|
2839
|
+
options.report.summary,
|
|
2840
|
+
options.report.memberAgentId,
|
|
2841
|
+
now
|
|
2842
|
+
)
|
|
2843
|
+
]
|
|
2844
|
+
}
|
|
2845
|
+
});
|
|
2846
|
+
await rebuildResponsibilities(options.rootPath);
|
|
2847
|
+
return {
|
|
2848
|
+
outcome: "applied",
|
|
2849
|
+
requestId,
|
|
2362
2850
|
taskId: options.report.taskId,
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
options.report.summary,
|
|
2369
|
-
options.report.memberAgentId,
|
|
2370
|
-
now
|
|
2371
|
-
)
|
|
2372
|
-
]
|
|
2373
|
-
}
|
|
2374
|
-
});
|
|
2375
|
-
await rebuildResponsibilities(options.rootPath);
|
|
2376
|
-
return {
|
|
2377
|
-
outcome: "applied",
|
|
2378
|
-
requestId: `turn-report:${options.report.taskId}`,
|
|
2379
|
-
taskId: options.report.taskId,
|
|
2380
|
-
message: `recorded turn report for ${options.report.taskId}`
|
|
2381
|
-
};
|
|
2851
|
+
message: `recorded turn report for ${options.report.taskId}`
|
|
2852
|
+
};
|
|
2853
|
+
} finally {
|
|
2854
|
+
await release();
|
|
2855
|
+
}
|
|
2382
2856
|
}
|
|
2383
2857
|
|
|
2384
2858
|
function buildSessionRuntimeIndex(metadata) {
|
|
@@ -3326,6 +3800,9 @@ const PROTOCOL_V3_CHANGES_PAGE_LIMIT = 200;
|
|
|
3326
3800
|
const PROTOCOL_V3_CAPABILITIES_WAIT_MS = 250;
|
|
3327
3801
|
const COMMITTED_SESSION_WRITE_ACK_TIMEOUT_MS = 5e3;
|
|
3328
3802
|
const RELIABLE_SESSION_MESSAGE_RETRY_DELAY_MS = 250;
|
|
3803
|
+
const LEGACY_CONTROL_ACK_HEADER$1 = Object.freeze({
|
|
3804
|
+
"x-happy-legacy-control-ack": "happy-org-1.7.3-legacy-write"
|
|
3805
|
+
});
|
|
3329
3806
|
function normalizeHappyOrgOptionalText(value) {
|
|
3330
3807
|
if (typeof value !== "string") {
|
|
3331
3808
|
return null;
|
|
@@ -3363,6 +3840,12 @@ function buildHappyOrgTurnReportSubmissionKey(messageId, report) {
|
|
|
3363
3840
|
report.summary.trim()
|
|
3364
3841
|
].join(":");
|
|
3365
3842
|
}
|
|
3843
|
+
function withLegacyControlAck$1(headers) {
|
|
3844
|
+
return {
|
|
3845
|
+
...headers ?? {},
|
|
3846
|
+
...LEGACY_CONTROL_ACK_HEADER$1
|
|
3847
|
+
};
|
|
3848
|
+
}
|
|
3366
3849
|
function parseHappyOrgFallbackEnvelope(rawEnvelope) {
|
|
3367
3850
|
try {
|
|
3368
3851
|
const parsed = JSON.parse(rawEnvelope);
|
|
@@ -3383,7 +3866,9 @@ function parseHappyOrgFallbackEnvelope(rawEnvelope) {
|
|
|
3383
3866
|
replyTo,
|
|
3384
3867
|
organizationId: typeof parsed.organization_id === "string" ? parsed.organization_id.trim() : null,
|
|
3385
3868
|
memberAgentId: typeof parsed.member_agent_id === "string" ? parsed.member_agent_id.trim() : null,
|
|
3386
|
-
supervisorAgentId: typeof parsed.supervisor_agent_id === "string" ? parsed.supervisor_agent_id.trim() : null
|
|
3869
|
+
supervisorAgentId: typeof parsed.supervisor_agent_id === "string" ? parsed.supervisor_agent_id.trim() : null,
|
|
3870
|
+
positionId: typeof parsed.position_id === "string" ? parsed.position_id.trim() : null,
|
|
3871
|
+
responsibilityId: typeof parsed.responsibility_id === "string" ? parsed.responsibility_id.trim() : null
|
|
3387
3872
|
};
|
|
3388
3873
|
} catch {
|
|
3389
3874
|
return null;
|
|
@@ -3400,13 +3885,17 @@ function buildHappyOrgFallbackUserMessage(rawEnvelope) {
|
|
|
3400
3885
|
taskId: envelope.taskId,
|
|
3401
3886
|
organizationId: envelope.organizationId,
|
|
3402
3887
|
memberAgentId: envelope.memberAgentId,
|
|
3403
|
-
supervisorAgentId: envelope.supervisorAgentId
|
|
3888
|
+
supervisorAgentId: envelope.supervisorAgentId,
|
|
3889
|
+
...envelope.positionId ? { positionId: envelope.positionId } : {},
|
|
3890
|
+
...envelope.responsibilityId ? { responsibilityId: envelope.responsibilityId } : {}
|
|
3404
3891
|
}
|
|
3405
3892
|
} : {},
|
|
3406
3893
|
replyContext: {
|
|
3407
3894
|
dispatchId: envelope.dispatchId,
|
|
3408
3895
|
scope: envelope.scope,
|
|
3409
|
-
replyTo: envelope.replyTo
|
|
3896
|
+
replyTo: envelope.replyTo,
|
|
3897
|
+
...envelope.positionId ? { positionId: envelope.positionId } : {},
|
|
3898
|
+
...envelope.responsibilityId ? { responsibilityId: envelope.responsibilityId } : {}
|
|
3410
3899
|
}
|
|
3411
3900
|
};
|
|
3412
3901
|
return {
|
|
@@ -4195,10 +4684,10 @@ class ApiSessionClient extends EventEmitter {
|
|
|
4195
4684
|
url,
|
|
4196
4685
|
body: payload,
|
|
4197
4686
|
signRequest: true,
|
|
4198
|
-
headers: {
|
|
4687
|
+
headers: withLegacyControlAck$1({
|
|
4199
4688
|
"Content-Type": "application/json",
|
|
4200
4689
|
"x-happy-organization-id": report.organizationId
|
|
4201
|
-
}
|
|
4690
|
+
})
|
|
4202
4691
|
}),
|
|
4203
4692
|
timeout: 1e4
|
|
4204
4693
|
});
|
|
@@ -4298,10 +4787,10 @@ class ApiSessionClient extends EventEmitter {
|
|
|
4298
4787
|
url,
|
|
4299
4788
|
body: payload,
|
|
4300
4789
|
signRequest: true,
|
|
4301
|
-
headers: {
|
|
4790
|
+
headers: withLegacyControlAck$1({
|
|
4302
4791
|
"Content-Type": "application/json",
|
|
4303
4792
|
"x-happy-organization-id": candidate.organizationId
|
|
4304
|
-
}
|
|
4793
|
+
})
|
|
4305
4794
|
}),
|
|
4306
4795
|
timeout: 1e4
|
|
4307
4796
|
});
|
|
@@ -5540,6 +6029,15 @@ async function deriveKey(master, usage, path) {
|
|
|
5540
6029
|
}
|
|
5541
6030
|
|
|
5542
6031
|
const SESSION_DATA_KEY_USAGE = "Happy Session Data Key";
|
|
6032
|
+
const LEGACY_CONTROL_ACK_HEADER = Object.freeze({
|
|
6033
|
+
"x-happy-legacy-control-ack": "happy-org-1.7.3-legacy-write"
|
|
6034
|
+
});
|
|
6035
|
+
function withLegacyControlAck(headers) {
|
|
6036
|
+
return {
|
|
6037
|
+
...headers ?? {},
|
|
6038
|
+
...LEGACY_CONTROL_ACK_HEADER
|
|
6039
|
+
};
|
|
6040
|
+
}
|
|
5543
6041
|
class ApiClient {
|
|
5544
6042
|
static async create(credential) {
|
|
5545
6043
|
return new ApiClient(credential);
|
|
@@ -5636,9 +6134,11 @@ class ApiClient {
|
|
|
5636
6134
|
allowed_paths: opts.allowedPaths,
|
|
5637
6135
|
forbidden_paths: opts.forbiddenPaths ?? [],
|
|
5638
6136
|
cross_scope_policy: "ask-first",
|
|
6137
|
+
position_id: opts.positionId ?? null,
|
|
6138
|
+
responsibility_id: opts.responsibilityId ?? null,
|
|
5639
6139
|
note: opts.note ?? null
|
|
5640
6140
|
},
|
|
5641
|
-
headers: { "x-happy-organization-id": opts.organizationId },
|
|
6141
|
+
headers: withLegacyControlAck({ "x-happy-organization-id": opts.organizationId }),
|
|
5642
6142
|
timeout: 1e4
|
|
5643
6143
|
});
|
|
5644
6144
|
return response.data.receipt;
|
|
@@ -5664,7 +6164,7 @@ class ApiClient {
|
|
|
5664
6164
|
status: opts.status,
|
|
5665
6165
|
note: opts.note ?? null
|
|
5666
6166
|
},
|
|
5667
|
-
headers: { "x-happy-organization-id": opts.organizationId },
|
|
6167
|
+
headers: withLegacyControlAck({ "x-happy-organization-id": opts.organizationId }),
|
|
5668
6168
|
timeout: 1e4
|
|
5669
6169
|
});
|
|
5670
6170
|
return response.data.receipt;
|