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
|
@@ -38,7 +38,7 @@ function _interopNamespaceDefault(e) {
|
|
|
38
38
|
var z__namespace = /*#__PURE__*/_interopNamespaceDefault(z);
|
|
39
39
|
|
|
40
40
|
var name = "happy-imou-cloud";
|
|
41
|
-
var version = "2.1.
|
|
41
|
+
var version = "2.1.30";
|
|
42
42
|
var description = "hicloud - Imou 企业定制版。关键是 happy!移动端远程 AI 编程工具,支持 Claude Code、Codex 和 Gemini CLI";
|
|
43
43
|
var author = "long.zhu";
|
|
44
44
|
var license = "MIT";
|
|
@@ -1230,7 +1230,9 @@ const HappyOrgTaskContextSchema = z.z.object({
|
|
|
1230
1230
|
taskId: z.z.string().min(1),
|
|
1231
1231
|
organizationId: z.z.string().min(1),
|
|
1232
1232
|
memberAgentId: z.z.string().min(1),
|
|
1233
|
-
supervisorAgentId: z.z.string().min(1)
|
|
1233
|
+
supervisorAgentId: z.z.string().min(1),
|
|
1234
|
+
positionId: z.z.string().min(1).optional().nullable(),
|
|
1235
|
+
responsibilityId: z.z.string().min(1).optional().nullable()
|
|
1234
1236
|
});
|
|
1235
1237
|
const HappyOrgTaskControlSchema = z.z.object({
|
|
1236
1238
|
action: z.z.enum(["terminate", "reopen"]),
|
|
@@ -1242,7 +1244,9 @@ const HappyOrgTaskControlSchema = z.z.object({
|
|
|
1242
1244
|
const HappyOrgReplyContextSchema = z.z.object({
|
|
1243
1245
|
dispatchId: z.z.string().min(1),
|
|
1244
1246
|
scope: z.z.string().min(1),
|
|
1245
|
-
replyTo: z.z.string().min(1)
|
|
1247
|
+
replyTo: z.z.string().min(1),
|
|
1248
|
+
positionId: z.z.string().min(1).optional().nullable(),
|
|
1249
|
+
responsibilityId: z.z.string().min(1).optional().nullable()
|
|
1246
1250
|
});
|
|
1247
1251
|
const HappyOrgDispatchBusinessAckStatusSchema = z.z.enum([
|
|
1248
1252
|
"accepted",
|
|
@@ -1595,6 +1599,15 @@ const AgentMessageSchema = z.z.object({
|
|
|
1595
1599
|
const MessageContentSchema = z.z.union([UserMessageSchema, AgentMessageSchema]);
|
|
1596
1600
|
|
|
1597
1601
|
const HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION = "1.7.3-local-repo-v1";
|
|
1602
|
+
const HAPPY_ORG_WRITER_LOCK_SCHEMA_VERSION = "1.7.3-writer-lock-v1";
|
|
1603
|
+
const WRITER_LOCK_LEASE_DURATION_SECONDS = 900;
|
|
1604
|
+
const WRITER_LOCK_RENEW_BEFORE_SECONDS = 300;
|
|
1605
|
+
const WRITER_LOCK_CEO_TAKEOVER_AFTER_SECONDS = 1800;
|
|
1606
|
+
const WRITER_LOCK_RENEW_INTERVAL_MS = Math.max(
|
|
1607
|
+
(WRITER_LOCK_LEASE_DURATION_SECONDS - WRITER_LOCK_RENEW_BEFORE_SECONDS) * 1e3,
|
|
1608
|
+
1e3
|
|
1609
|
+
);
|
|
1610
|
+
let writerLockSequence = 0;
|
|
1598
1611
|
const TaskStatusSchema = z.z.enum([
|
|
1599
1612
|
"draft",
|
|
1600
1613
|
"ready",
|
|
@@ -1678,10 +1691,46 @@ const UpdateTaskRequestSchema = BaseRequestSchema.extend({
|
|
|
1678
1691
|
expectedRevision: z.z.number().nullable(),
|
|
1679
1692
|
nextState: TaskUpdatePayloadSchema
|
|
1680
1693
|
});
|
|
1694
|
+
const AgentUpsertPayloadSchema = z.z.object({
|
|
1695
|
+
id: z.z.string().min(1),
|
|
1696
|
+
slug: z.z.string().min(1),
|
|
1697
|
+
role: z.z.string().min(1),
|
|
1698
|
+
name: z.z.string().min(1),
|
|
1699
|
+
sessionId: z.z.string().min(1),
|
|
1700
|
+
sessionTitle: z.z.string().min(1),
|
|
1701
|
+
runtimePath: z.z.string().nullable(),
|
|
1702
|
+
workingDirectoryRelativePath: z.z.string().nullable().optional(),
|
|
1703
|
+
machineId: z.z.string().nullable(),
|
|
1704
|
+
backend: z.z.string().nullable(),
|
|
1705
|
+
supervisorAgentId: z.z.string().nullable(),
|
|
1706
|
+
createdAt: z.z.number(),
|
|
1707
|
+
updatedAt: z.z.number()
|
|
1708
|
+
});
|
|
1709
|
+
const PositionUpsertPayloadSchema = z.z.object({
|
|
1710
|
+
positionId: z.z.string().min(1),
|
|
1711
|
+
slug: z.z.string().min(1),
|
|
1712
|
+
label: z.z.string().min(1),
|
|
1713
|
+
role: z.z.string().nullable(),
|
|
1714
|
+
workingDirectoryRelativePath: z.z.string().nullable(),
|
|
1715
|
+
agentId: z.z.string().nullable(),
|
|
1716
|
+
agentName: z.z.string().nullable(),
|
|
1717
|
+
supervisorAgentId: z.z.string().nullable(),
|
|
1718
|
+
responsibilityIds: z.z.array(z.z.string()),
|
|
1719
|
+
occupancyStatus: z.z.enum(["occupied", "vacancy", "assigned", "active"]),
|
|
1720
|
+
createdAt: z.z.number(),
|
|
1721
|
+
updatedAt: z.z.number()
|
|
1722
|
+
});
|
|
1723
|
+
const UpsertAgentRequestSchema = BaseRequestSchema.extend({
|
|
1724
|
+
requestType: z.z.literal("upsert_agent"),
|
|
1725
|
+
agent: AgentUpsertPayloadSchema,
|
|
1726
|
+
position: PositionUpsertPayloadSchema,
|
|
1727
|
+
expectedRevision: z.z.number().nullable()
|
|
1728
|
+
});
|
|
1681
1729
|
const RequestSchema = z.z.discriminatedUnion("requestType", [
|
|
1682
1730
|
ReplaceGoalRequestSchema,
|
|
1683
1731
|
CreateTaskRequestSchema,
|
|
1684
|
-
UpdateTaskRequestSchema
|
|
1732
|
+
UpdateTaskRequestSchema,
|
|
1733
|
+
UpsertAgentRequestSchema
|
|
1685
1734
|
]);
|
|
1686
1735
|
const RevisionEnvelopeSchema = (value) => z.z.object({
|
|
1687
1736
|
schemaVersion: z.z.literal(HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION),
|
|
@@ -1694,16 +1743,23 @@ const PositionValueSchema = z.z.object({
|
|
|
1694
1743
|
positionId: z.z.string(),
|
|
1695
1744
|
slug: z.z.string(),
|
|
1696
1745
|
label: z.z.string(),
|
|
1697
|
-
role: z.z.string(),
|
|
1698
|
-
|
|
1699
|
-
|
|
1746
|
+
role: z.z.string().nullable().optional(),
|
|
1747
|
+
workingDirectoryRelativePath: z.z.string().nullable().optional(),
|
|
1748
|
+
agentId: z.z.string().nullable().optional(),
|
|
1749
|
+
agentName: z.z.string().nullable().optional(),
|
|
1700
1750
|
supervisorAgentId: z.z.string().nullable(),
|
|
1701
1751
|
responsibilityIds: z.z.array(z.z.string()),
|
|
1702
|
-
occupancyStatus: z.z.enum(["occupied", "vacancy"]),
|
|
1752
|
+
occupancyStatus: z.z.enum(["occupied", "vacancy", "assigned", "active"]),
|
|
1703
1753
|
truthPath: z.z.string(),
|
|
1704
1754
|
createdAt: z.z.number(),
|
|
1705
1755
|
updatedAt: z.z.number()
|
|
1706
1756
|
});
|
|
1757
|
+
const WorkspaceValueSchema = z.z.object({
|
|
1758
|
+
positionId: z.z.string(),
|
|
1759
|
+
slug: z.z.string(),
|
|
1760
|
+
relativePath: z.z.string().nullable(),
|
|
1761
|
+
updatedAt: z.z.number()
|
|
1762
|
+
});
|
|
1707
1763
|
const ResponsibilityValueSchema = z.z.object({
|
|
1708
1764
|
responsibilityId: z.z.string(),
|
|
1709
1765
|
title: z.z.string(),
|
|
@@ -1713,6 +1769,7 @@ const ResponsibilityValueSchema = z.z.object({
|
|
|
1713
1769
|
status: z.z.enum(["vacancy", "assigned", "in_progress"]),
|
|
1714
1770
|
memberAgentId: z.z.string().nullable(),
|
|
1715
1771
|
memberName: z.z.string().nullable(),
|
|
1772
|
+
role: z.z.string().nullable().optional(),
|
|
1716
1773
|
activeTaskId: z.z.string().nullable(),
|
|
1717
1774
|
createdAt: z.z.number(),
|
|
1718
1775
|
updatedAt: z.z.number()
|
|
@@ -1795,11 +1852,45 @@ const LogValueSchema = z.z.object({
|
|
|
1795
1852
|
});
|
|
1796
1853
|
const GoalEnvelopeSchema = RevisionEnvelopeSchema(GoalSchema);
|
|
1797
1854
|
const PositionEnvelopeSchema = RevisionEnvelopeSchema(PositionValueSchema);
|
|
1855
|
+
const WorkspaceEnvelopeSchema = RevisionEnvelopeSchema(WorkspaceValueSchema);
|
|
1798
1856
|
const ResponsibilityEnvelopeSchema = RevisionEnvelopeSchema(z.z.array(ResponsibilityValueSchema));
|
|
1799
1857
|
const TaskEnvelopeSchema = RevisionEnvelopeSchema(TaskValueSchema);
|
|
1800
1858
|
const WorkflowEnvelopeSchema = RevisionEnvelopeSchema(WorkflowValueSchema);
|
|
1801
1859
|
const ResultEnvelopeSchema = RevisionEnvelopeSchema(ResultValueSchema);
|
|
1802
1860
|
const LogEnvelopeSchema = RevisionEnvelopeSchema(LogValueSchema);
|
|
1861
|
+
const WriterLockRecordSchema = z.z.object({
|
|
1862
|
+
schemaVersion: z.z.literal(HAPPY_ORG_WRITER_LOCK_SCHEMA_VERSION),
|
|
1863
|
+
lockId: z.z.string().min(1),
|
|
1864
|
+
target: z.z.string().min(1),
|
|
1865
|
+
holderType: z.z.literal("cli-writer"),
|
|
1866
|
+
holderId: z.z.string().min(1),
|
|
1867
|
+
requestId: z.z.string().min(1),
|
|
1868
|
+
pid: z.z.number().int().nonnegative(),
|
|
1869
|
+
acquiredAt: z.z.number().int().nonnegative(),
|
|
1870
|
+
renewedAt: z.z.number().int().nonnegative(),
|
|
1871
|
+
expiresAt: z.z.number().int().nonnegative(),
|
|
1872
|
+
leaseDurationSeconds: z.z.literal(WRITER_LOCK_LEASE_DURATION_SECONDS),
|
|
1873
|
+
renewBeforeSeconds: z.z.literal(WRITER_LOCK_RENEW_BEFORE_SECONDS),
|
|
1874
|
+
ceoTakeoverAfterSeconds: z.z.literal(WRITER_LOCK_CEO_TAKEOVER_AFTER_SECONDS),
|
|
1875
|
+
takeoverOf: z.z.object({
|
|
1876
|
+
lockId: z.z.string().min(1),
|
|
1877
|
+
holderId: z.z.string().min(1),
|
|
1878
|
+
requestId: z.z.string().min(1),
|
|
1879
|
+
renewedAt: z.z.number().int().nonnegative()
|
|
1880
|
+
}).nullable()
|
|
1881
|
+
});
|
|
1882
|
+
class WriterLockConflictError extends Error {
|
|
1883
|
+
state;
|
|
1884
|
+
target;
|
|
1885
|
+
existing;
|
|
1886
|
+
constructor(state, target, message, existing = null) {
|
|
1887
|
+
super(message);
|
|
1888
|
+
this.name = "WriterLockConflictError";
|
|
1889
|
+
this.state = state;
|
|
1890
|
+
this.target = target;
|
|
1891
|
+
this.existing = existing;
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1803
1894
|
function normalizePortablePath(value) {
|
|
1804
1895
|
const trimmed = value?.trim();
|
|
1805
1896
|
if (!trimmed) {
|
|
@@ -1807,14 +1898,37 @@ function normalizePortablePath(value) {
|
|
|
1807
1898
|
}
|
|
1808
1899
|
return trimmed.replace(/\\/g, "/").replace(/\/{2,}/g, "/").replace(/\/$/, "");
|
|
1809
1900
|
}
|
|
1901
|
+
function normalizeRelativePath(value) {
|
|
1902
|
+
const segments = (value ?? "").split(/[\\/]+/).map((segment) => segment.trim()).filter(Boolean).filter((segment) => segment !== "." && segment !== "..");
|
|
1903
|
+
return segments.length > 0 ? segments.join("/") : null;
|
|
1904
|
+
}
|
|
1905
|
+
function resolveRelativePath(rootPath, relativePath) {
|
|
1906
|
+
const normalizedRelativePath = normalizeRelativePath(relativePath);
|
|
1907
|
+
if (!normalizedRelativePath) {
|
|
1908
|
+
return null;
|
|
1909
|
+
}
|
|
1910
|
+
return path.join(rootPath, ...normalizedRelativePath.split("/"));
|
|
1911
|
+
}
|
|
1912
|
+
function normalizeOccupancyStatus(value) {
|
|
1913
|
+
switch (value) {
|
|
1914
|
+
case "vacancy":
|
|
1915
|
+
return "vacancy";
|
|
1916
|
+
case "active":
|
|
1917
|
+
return "active";
|
|
1918
|
+
case "assigned":
|
|
1919
|
+
case "occupied":
|
|
1920
|
+
default:
|
|
1921
|
+
return "assigned";
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1810
1924
|
function normalizeAdviceRoute(value) {
|
|
1811
1925
|
return value === "gm" || value === "lead" || value === "user" ? value : null;
|
|
1812
1926
|
}
|
|
1813
1927
|
function buildResponsibilityId(slug) {
|
|
1814
1928
|
return `responsibility-${slug}`;
|
|
1815
1929
|
}
|
|
1816
|
-
function
|
|
1817
|
-
return
|
|
1930
|
+
function buildPositionRoot(rootPath, slug) {
|
|
1931
|
+
return path.join(rootPath, "positions", slug);
|
|
1818
1932
|
}
|
|
1819
1933
|
function toTaskSlug(taskId, title) {
|
|
1820
1934
|
const slug = title.trim().toLowerCase().replace(/[^a-z0-9\u4e00-\u9fa5]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -1859,6 +1973,74 @@ function buildRequestFilePath(rootPath, requestId) {
|
|
|
1859
1973
|
function buildTaskRoot(rootPath, taskId) {
|
|
1860
1974
|
return path.join(rootPath, "tasks", taskId);
|
|
1861
1975
|
}
|
|
1976
|
+
function nextWriterLockId() {
|
|
1977
|
+
writerLockSequence += 1;
|
|
1978
|
+
return `writer-lock-${process.pid}-${Date.now().toString(36)}-${writerLockSequence.toString(36)}`;
|
|
1979
|
+
}
|
|
1980
|
+
function normalizeWriterLockTarget(target) {
|
|
1981
|
+
return target.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "writer-lock";
|
|
1982
|
+
}
|
|
1983
|
+
function buildWriterLockPath(rootPath, target) {
|
|
1984
|
+
return path.join(rootPath, ".runtime", "locks", `${normalizeWriterLockTarget(target)}.lock.json`);
|
|
1985
|
+
}
|
|
1986
|
+
function buildWriterLockRecord(params) {
|
|
1987
|
+
const now = params.now ?? Date.now();
|
|
1988
|
+
return {
|
|
1989
|
+
schemaVersion: HAPPY_ORG_WRITER_LOCK_SCHEMA_VERSION,
|
|
1990
|
+
lockId: nextWriterLockId(),
|
|
1991
|
+
target: params.target,
|
|
1992
|
+
holderType: "cli-writer",
|
|
1993
|
+
holderId: params.holderId,
|
|
1994
|
+
requestId: params.requestId,
|
|
1995
|
+
pid: process.pid,
|
|
1996
|
+
acquiredAt: now,
|
|
1997
|
+
renewedAt: now,
|
|
1998
|
+
expiresAt: now + WRITER_LOCK_LEASE_DURATION_SECONDS * 1e3,
|
|
1999
|
+
leaseDurationSeconds: WRITER_LOCK_LEASE_DURATION_SECONDS,
|
|
2000
|
+
renewBeforeSeconds: WRITER_LOCK_RENEW_BEFORE_SECONDS,
|
|
2001
|
+
ceoTakeoverAfterSeconds: WRITER_LOCK_CEO_TAKEOVER_AFTER_SECONDS,
|
|
2002
|
+
takeoverOf: params.takeoverOf ? {
|
|
2003
|
+
lockId: params.takeoverOf.lockId,
|
|
2004
|
+
holderId: params.takeoverOf.holderId,
|
|
2005
|
+
requestId: params.takeoverOf.requestId,
|
|
2006
|
+
renewedAt: params.takeoverOf.renewedAt
|
|
2007
|
+
} : null
|
|
2008
|
+
};
|
|
2009
|
+
}
|
|
2010
|
+
function getWriterLockAgeMs(lock, now) {
|
|
2011
|
+
return Math.max(now - Math.max(lock.renewedAt, lock.acquiredAt), 0);
|
|
2012
|
+
}
|
|
2013
|
+
async function removeWriterLockFile(lockPath) {
|
|
2014
|
+
await promises.unlink(lockPath).catch(async () => {
|
|
2015
|
+
await promises.rm(lockPath, { force: true });
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
2018
|
+
async function readWriterLockRecord(lockPath) {
|
|
2019
|
+
const payload = await readJson(lockPath);
|
|
2020
|
+
const parsed = WriterLockRecordSchema.safeParse(payload);
|
|
2021
|
+
return parsed.success ? parsed.data : null;
|
|
2022
|
+
}
|
|
2023
|
+
function isFileExistsError(error) {
|
|
2024
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
|
|
2025
|
+
}
|
|
2026
|
+
function isFileNotFoundError(error) {
|
|
2027
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
2028
|
+
}
|
|
2029
|
+
function getWriterLockTargetsForRequest(request) {
|
|
2030
|
+
if (request.requestType === "replace_goal") {
|
|
2031
|
+
return ["goal"];
|
|
2032
|
+
}
|
|
2033
|
+
if (request.requestType === "upsert_agent") {
|
|
2034
|
+
return [`position-${request.position.positionId}`, "responsibilities"];
|
|
2035
|
+
}
|
|
2036
|
+
if (request.requestType === "create_task") {
|
|
2037
|
+
return ["task-create", "responsibilities"];
|
|
2038
|
+
}
|
|
2039
|
+
return [`task-${request.taskId}`, "responsibilities"];
|
|
2040
|
+
}
|
|
2041
|
+
function getWriterLockTargetsForTurnReport(taskId) {
|
|
2042
|
+
return [`task-${taskId}`, "responsibilities"];
|
|
2043
|
+
}
|
|
1862
2044
|
async function loadTaskEnvelope(rootPath, taskId) {
|
|
1863
2045
|
const taskRoot = buildTaskRoot(rootPath, taskId);
|
|
1864
2046
|
const task = await readEnvelope(path.join(taskRoot, "TASK.json"), TaskEnvelopeSchema);
|
|
@@ -1883,15 +2065,52 @@ async function nextTaskId(rootPath) {
|
|
|
1883
2065
|
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;
|
|
1884
2066
|
return `T-${String(nextNumber).padStart(3, "0")}`;
|
|
1885
2067
|
}
|
|
1886
|
-
async function
|
|
2068
|
+
async function listPositionEntries(rootPath) {
|
|
1887
2069
|
try {
|
|
1888
2070
|
const entries = await promises.readdir(path.join(rootPath, "positions"), { withFileTypes: true });
|
|
1889
|
-
const
|
|
1890
|
-
|
|
2071
|
+
const positionEntries = await Promise.all(entries.map(async (entry) => {
|
|
2072
|
+
if (entry.isDirectory()) {
|
|
2073
|
+
const positionRoot = path.join(rootPath, "positions", entry.name);
|
|
2074
|
+
const position = await readEnvelope(path.join(positionRoot, "POSITION.json"), PositionEnvelopeSchema);
|
|
2075
|
+
if (!position) {
|
|
2076
|
+
return null;
|
|
2077
|
+
}
|
|
2078
|
+
const workspace = await readEnvelope(path.join(positionRoot, "WORKSPACE.json"), WorkspaceEnvelopeSchema);
|
|
2079
|
+
return {
|
|
2080
|
+
position,
|
|
2081
|
+
workspace,
|
|
2082
|
+
source: "directory"
|
|
2083
|
+
};
|
|
2084
|
+
}
|
|
2085
|
+
if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
2086
|
+
const position = await readEnvelope(path.join(rootPath, "positions", entry.name), PositionEnvelopeSchema);
|
|
2087
|
+
if (!position) {
|
|
2088
|
+
return null;
|
|
2089
|
+
}
|
|
2090
|
+
return {
|
|
2091
|
+
position,
|
|
2092
|
+
workspace: null,
|
|
2093
|
+
source: "legacy-file"
|
|
2094
|
+
};
|
|
2095
|
+
}
|
|
2096
|
+
return null;
|
|
2097
|
+
}));
|
|
2098
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
2099
|
+
for (const entry of positionEntries.filter((value) => value !== null)) {
|
|
2100
|
+
const key = entry.position.value.positionId;
|
|
2101
|
+
const existing = deduped.get(key) ?? null;
|
|
2102
|
+
if (!existing || existing.source === "legacy-file" && entry.source === "directory") {
|
|
2103
|
+
deduped.set(key, entry);
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
return [...deduped.values()];
|
|
1891
2107
|
} catch {
|
|
1892
2108
|
return [];
|
|
1893
2109
|
}
|
|
1894
2110
|
}
|
|
2111
|
+
async function listPositionEnvelopes(rootPath) {
|
|
2112
|
+
return (await listPositionEntries(rootPath)).map((entry) => entry.position);
|
|
2113
|
+
}
|
|
1895
2114
|
async function readResponsibilities(rootPath) {
|
|
1896
2115
|
return await readEnvelope(path.join(rootPath, "RESPONSIBILITIES.json"), ResponsibilityEnvelopeSchema);
|
|
1897
2116
|
}
|
|
@@ -1924,16 +2143,18 @@ async function rebuildResponsibilities(rootPath) {
|
|
|
1924
2143
|
const existing = await readResponsibilities(rootPath);
|
|
1925
2144
|
const previousRevision = existing?.revision ?? 0;
|
|
1926
2145
|
const responsibilities = positions.map((position) => {
|
|
1927
|
-
const
|
|
2146
|
+
const responsibilityId = position.value.responsibilityIds[0] ?? buildResponsibilityId(position.value.slug);
|
|
2147
|
+
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;
|
|
1928
2148
|
return {
|
|
1929
|
-
responsibilityId
|
|
1930
|
-
title: `${position.value.
|
|
2149
|
+
responsibilityId,
|
|
2150
|
+
title: position.value.label || position.value.agentName || `${position.value.slug} responsibility`,
|
|
1931
2151
|
summary: activeTask?.summary ?? null,
|
|
1932
|
-
positionId:
|
|
1933
|
-
participantType: "agent",
|
|
1934
|
-
status: activeTask ? "in_progress" : "assigned",
|
|
1935
|
-
memberAgentId: position.value.agentId,
|
|
1936
|
-
memberName: position.value.agentName,
|
|
2152
|
+
positionId: position.value.positionId,
|
|
2153
|
+
participantType: position.value.agentId ? "agent" : "vacancy",
|
|
2154
|
+
status: position.value.agentId ? activeTask ? "in_progress" : "assigned" : "vacancy",
|
|
2155
|
+
memberAgentId: position.value.agentId ?? null,
|
|
2156
|
+
memberName: position.value.agentName ?? null,
|
|
2157
|
+
role: position.value.role ?? null,
|
|
1937
2158
|
activeTaskId: activeTask?.taskId ?? null,
|
|
1938
2159
|
createdAt: position.value.createdAt,
|
|
1939
2160
|
updatedAt: Math.max(position.value.updatedAt, activeTask?.updatedAt ?? 0)
|
|
@@ -2004,12 +2225,104 @@ async function applyReplaceGoalRequest(rootPath, request) {
|
|
|
2004
2225
|
message: "goal updated"
|
|
2005
2226
|
};
|
|
2006
2227
|
}
|
|
2228
|
+
async function applyUpsertAgentRequest(rootPath, request) {
|
|
2229
|
+
const positionEntries = await listPositionEntries(rootPath);
|
|
2230
|
+
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;
|
|
2231
|
+
if (request.expectedRevision !== null && existingPosition?.position.revision !== request.expectedRevision) {
|
|
2232
|
+
const message = existingPosition ? `Position revision mismatch: expected ${request.expectedRevision}, got ${existingPosition.position.revision}` : `Position ${request.position.positionId} not found for expected revision ${request.expectedRevision}`;
|
|
2233
|
+
await updateRequestRecord(rootPath, request, {
|
|
2234
|
+
requestState: "recovery_required",
|
|
2235
|
+
processedAt: Date.now(),
|
|
2236
|
+
resultMessage: message
|
|
2237
|
+
});
|
|
2238
|
+
return {
|
|
2239
|
+
outcome: "recovery_required",
|
|
2240
|
+
requestId: request.requestId,
|
|
2241
|
+
taskId: null,
|
|
2242
|
+
message
|
|
2243
|
+
};
|
|
2244
|
+
}
|
|
2245
|
+
const positionRoot = buildPositionRoot(rootPath, request.position.slug);
|
|
2246
|
+
const now = Date.now();
|
|
2247
|
+
const positionValue = {
|
|
2248
|
+
positionId: request.position.positionId,
|
|
2249
|
+
slug: request.position.slug,
|
|
2250
|
+
label: request.position.label,
|
|
2251
|
+
role: request.position.role ?? request.agent.role,
|
|
2252
|
+
workingDirectoryRelativePath: normalizeRelativePath(
|
|
2253
|
+
request.position.workingDirectoryRelativePath ?? request.agent.workingDirectoryRelativePath
|
|
2254
|
+
),
|
|
2255
|
+
agentId: request.position.agentId ?? request.agent.id,
|
|
2256
|
+
agentName: request.position.agentName ?? request.agent.name,
|
|
2257
|
+
supervisorAgentId: request.position.supervisorAgentId ?? request.agent.supervisorAgentId,
|
|
2258
|
+
responsibilityIds: request.position.responsibilityIds.length > 0 ? request.position.responsibilityIds : [buildResponsibilityId(request.position.slug)],
|
|
2259
|
+
occupancyStatus: normalizeOccupancyStatus(request.position.occupancyStatus),
|
|
2260
|
+
truthPath: `positions/${request.position.slug}/POSITION.json`,
|
|
2261
|
+
createdAt: request.position.createdAt,
|
|
2262
|
+
updatedAt: request.agent.updatedAt
|
|
2263
|
+
};
|
|
2264
|
+
const existingWorkspace = existingPosition?.workspace ?? null;
|
|
2265
|
+
const workspaceValue = {
|
|
2266
|
+
positionId: request.position.positionId,
|
|
2267
|
+
slug: request.position.slug,
|
|
2268
|
+
relativePath: normalizeRelativePath(
|
|
2269
|
+
request.position.workingDirectoryRelativePath ?? request.agent.workingDirectoryRelativePath
|
|
2270
|
+
),
|
|
2271
|
+
updatedAt: request.agent.updatedAt
|
|
2272
|
+
};
|
|
2273
|
+
const runtimeValue = {
|
|
2274
|
+
agentId: request.agent.id,
|
|
2275
|
+
slug: request.agent.slug,
|
|
2276
|
+
role: request.agent.role,
|
|
2277
|
+
sessionId: request.agent.sessionId,
|
|
2278
|
+
sessionTitle: request.agent.sessionTitle,
|
|
2279
|
+
runtimePath: request.agent.runtimePath,
|
|
2280
|
+
assignedWorkingDirectoryRelativePath: workspaceValue.relativePath,
|
|
2281
|
+
assignedWorkingDirectoryPath: resolveRelativePath(rootPath, workspaceValue.relativePath),
|
|
2282
|
+
machineId: request.agent.machineId,
|
|
2283
|
+
backend: request.agent.backend,
|
|
2284
|
+
updatedAt: request.agent.updatedAt
|
|
2285
|
+
};
|
|
2286
|
+
await ensureDir(positionRoot);
|
|
2287
|
+
await writeEnvelope(path.join(positionRoot, "POSITION.json"), {
|
|
2288
|
+
schemaVersion: HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION,
|
|
2289
|
+
revision: existingPosition && JSON.stringify(existingPosition.position.value) === JSON.stringify(positionValue) ? existingPosition.position.revision : (existingPosition?.position.revision ?? 0) + 1,
|
|
2290
|
+
updatedAt: now,
|
|
2291
|
+
updatedBy: request.agent.id,
|
|
2292
|
+
value: positionValue
|
|
2293
|
+
});
|
|
2294
|
+
await writeEnvelope(path.join(positionRoot, "WORKSPACE.json"), {
|
|
2295
|
+
schemaVersion: HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION,
|
|
2296
|
+
revision: existingWorkspace && JSON.stringify(existingWorkspace.value) === JSON.stringify(workspaceValue) ? existingWorkspace.revision : (existingWorkspace?.revision ?? 0) + 1,
|
|
2297
|
+
updatedAt: now,
|
|
2298
|
+
updatedBy: request.agent.id,
|
|
2299
|
+
value: workspaceValue
|
|
2300
|
+
});
|
|
2301
|
+
await writeJson(path.join(rootPath, ".runtime", "agents", `${request.agent.slug}.json`), runtimeValue);
|
|
2302
|
+
if (existingPosition?.source === "legacy-file") {
|
|
2303
|
+
await promises.rm(path.join(rootPath, "positions", `${existingPosition.position.value.positionId}.json`), { force: true });
|
|
2304
|
+
}
|
|
2305
|
+
await rebuildResponsibilities(rootPath);
|
|
2306
|
+
await updateRequestRecord(rootPath, request, {
|
|
2307
|
+
requestState: "applied",
|
|
2308
|
+
processedAt: now,
|
|
2309
|
+
resultMessage: `upserted ${request.agent.id}`
|
|
2310
|
+
});
|
|
2311
|
+
return {
|
|
2312
|
+
outcome: "applied",
|
|
2313
|
+
requestId: request.requestId,
|
|
2314
|
+
taskId: null,
|
|
2315
|
+
message: `upserted ${request.agent.id}`
|
|
2316
|
+
};
|
|
2317
|
+
}
|
|
2007
2318
|
async function applyCreateTaskRequest(rootPath, request) {
|
|
2008
2319
|
const taskId = await nextTaskId(rootPath);
|
|
2009
2320
|
const taskRoot = buildTaskRoot(rootPath, taskId);
|
|
2010
2321
|
const createdAt = Date.now();
|
|
2011
2322
|
const title = request.title.trim();
|
|
2012
2323
|
const position = (await listPositionEnvelopes(rootPath)).find((entry) => entry.value.agentId === request.ownerAgentId) ?? null;
|
|
2324
|
+
const responsibilityId = position?.value.responsibilityIds[0] ?? (position ? buildResponsibilityId(position.value.slug) : null);
|
|
2325
|
+
const responsibilityLabel = position?.value.label ?? `${request.ownerName} responsibility`;
|
|
2013
2326
|
const taskValue = {
|
|
2014
2327
|
taskId,
|
|
2015
2328
|
slug: toTaskSlug(taskId, title),
|
|
@@ -2017,8 +2330,8 @@ async function applyCreateTaskRequest(rootPath, request) {
|
|
|
2017
2330
|
ownerAgentId: request.ownerAgentId,
|
|
2018
2331
|
ownerName: request.ownerName,
|
|
2019
2332
|
ownerSessionId: request.ownerSessionId,
|
|
2020
|
-
responsibilityId
|
|
2021
|
-
responsibilityLabel
|
|
2333
|
+
responsibilityId,
|
|
2334
|
+
responsibilityLabel,
|
|
2022
2335
|
activeOwnerAgentId: null,
|
|
2023
2336
|
hasActiveOwner: false,
|
|
2024
2337
|
path: request.path,
|
|
@@ -2236,19 +2549,127 @@ async function applyUpdateTaskRequest(rootPath, request) {
|
|
|
2236
2549
|
message: `updated ${request.taskId}`
|
|
2237
2550
|
};
|
|
2238
2551
|
}
|
|
2239
|
-
async function
|
|
2240
|
-
const
|
|
2552
|
+
async function renewSingleWriterLock(lockPath, lockId) {
|
|
2553
|
+
const existing = await readWriterLockRecord(lockPath);
|
|
2554
|
+
if (!existing || existing.lockId !== lockId) {
|
|
2555
|
+
return;
|
|
2556
|
+
}
|
|
2557
|
+
const now = Date.now();
|
|
2558
|
+
await writeJson(lockPath, {
|
|
2559
|
+
...existing,
|
|
2560
|
+
renewedAt: now,
|
|
2561
|
+
expiresAt: now + WRITER_LOCK_LEASE_DURATION_SECONDS * 1e3
|
|
2562
|
+
});
|
|
2563
|
+
}
|
|
2564
|
+
async function acquireSingleWriterLock(params) {
|
|
2565
|
+
const lockPath = buildWriterLockPath(params.rootPath, params.target);
|
|
2241
2566
|
await ensureDir(path.dirname(lockPath));
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2567
|
+
let takeoverOf = null;
|
|
2568
|
+
while (true) {
|
|
2569
|
+
const now = Date.now();
|
|
2570
|
+
const record = buildWriterLockRecord({
|
|
2571
|
+
target: params.target,
|
|
2572
|
+
holderId: params.holderId,
|
|
2573
|
+
requestId: params.requestId,
|
|
2574
|
+
takeoverOf,
|
|
2575
|
+
now
|
|
2251
2576
|
});
|
|
2577
|
+
try {
|
|
2578
|
+
const handle = await promises.open(lockPath, "wx");
|
|
2579
|
+
await handle.writeFile(JSON.stringify(record, null, 2), "utf8");
|
|
2580
|
+
await handle.close();
|
|
2581
|
+
const renewTimer = setInterval(() => {
|
|
2582
|
+
void renewSingleWriterLock(lockPath, record.lockId);
|
|
2583
|
+
}, WRITER_LOCK_RENEW_INTERVAL_MS);
|
|
2584
|
+
renewTimer.unref?.();
|
|
2585
|
+
return async () => {
|
|
2586
|
+
clearInterval(renewTimer);
|
|
2587
|
+
const existing = await readWriterLockRecord(lockPath);
|
|
2588
|
+
if (!existing || existing.lockId !== record.lockId) {
|
|
2589
|
+
return;
|
|
2590
|
+
}
|
|
2591
|
+
await removeWriterLockFile(lockPath);
|
|
2592
|
+
};
|
|
2593
|
+
} catch (error) {
|
|
2594
|
+
if (!isFileExistsError(error)) {
|
|
2595
|
+
throw error;
|
|
2596
|
+
}
|
|
2597
|
+
const existing = await readWriterLockRecord(lockPath);
|
|
2598
|
+
if (!existing) {
|
|
2599
|
+
try {
|
|
2600
|
+
const info = await promises.stat(lockPath);
|
|
2601
|
+
const ageMs2 = Math.max(now - info.mtimeMs, 0);
|
|
2602
|
+
if (ageMs2 >= WRITER_LOCK_CEO_TAKEOVER_AFTER_SECONDS * 1e3) {
|
|
2603
|
+
await removeWriterLockFile(lockPath);
|
|
2604
|
+
takeoverOf = null;
|
|
2605
|
+
continue;
|
|
2606
|
+
}
|
|
2607
|
+
} catch (statError) {
|
|
2608
|
+
if (isFileNotFoundError(statError)) {
|
|
2609
|
+
takeoverOf = null;
|
|
2610
|
+
continue;
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
throw new WriterLockConflictError(
|
|
2614
|
+
"invalid",
|
|
2615
|
+
params.target,
|
|
2616
|
+
`Lock ${params.target} exists but is unreadable; mark recovery before retrying.`
|
|
2617
|
+
);
|
|
2618
|
+
}
|
|
2619
|
+
const ageMs = getWriterLockAgeMs(existing, now);
|
|
2620
|
+
if (ageMs >= WRITER_LOCK_CEO_TAKEOVER_AFTER_SECONDS * 1e3) {
|
|
2621
|
+
await removeWriterLockFile(lockPath);
|
|
2622
|
+
takeoverOf = existing;
|
|
2623
|
+
continue;
|
|
2624
|
+
}
|
|
2625
|
+
if (ageMs >= WRITER_LOCK_LEASE_DURATION_SECONDS * 1e3) {
|
|
2626
|
+
throw new WriterLockConflictError(
|
|
2627
|
+
"stale",
|
|
2628
|
+
params.target,
|
|
2629
|
+
`Lock ${params.target} is stale for ${Math.floor(ageMs / 1e3)}s and requires CEO recovery before takeover.`,
|
|
2630
|
+
existing
|
|
2631
|
+
);
|
|
2632
|
+
}
|
|
2633
|
+
throw new WriterLockConflictError(
|
|
2634
|
+
"active",
|
|
2635
|
+
params.target,
|
|
2636
|
+
`Lock ${params.target} is still held by ${existing.holderId} for request ${existing.requestId}.`,
|
|
2637
|
+
existing
|
|
2638
|
+
);
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
async function acquireWriterLocks(params) {
|
|
2643
|
+
const holderId = `cli-writer:${process.pid}`;
|
|
2644
|
+
const releaseSteps = [];
|
|
2645
|
+
const orderedTargets = [...new Set(params.targets)].sort();
|
|
2646
|
+
try {
|
|
2647
|
+
for (const target of orderedTargets) {
|
|
2648
|
+
releaseSteps.push(await acquireSingleWriterLock({
|
|
2649
|
+
rootPath: params.rootPath,
|
|
2650
|
+
target,
|
|
2651
|
+
holderId,
|
|
2652
|
+
requestId: params.requestId
|
|
2653
|
+
}));
|
|
2654
|
+
}
|
|
2655
|
+
} catch (error) {
|
|
2656
|
+
while (releaseSteps.length > 0) {
|
|
2657
|
+
const release = releaseSteps.pop();
|
|
2658
|
+
if (!release) {
|
|
2659
|
+
continue;
|
|
2660
|
+
}
|
|
2661
|
+
await release();
|
|
2662
|
+
}
|
|
2663
|
+
throw error;
|
|
2664
|
+
}
|
|
2665
|
+
return async () => {
|
|
2666
|
+
while (releaseSteps.length > 0) {
|
|
2667
|
+
const release = releaseSteps.pop();
|
|
2668
|
+
if (!release) {
|
|
2669
|
+
continue;
|
|
2670
|
+
}
|
|
2671
|
+
await release();
|
|
2672
|
+
}
|
|
2252
2673
|
};
|
|
2253
2674
|
}
|
|
2254
2675
|
async function processSingleRequest(rootPath, request) {
|
|
@@ -2261,11 +2682,38 @@ async function processSingleRequest(rootPath, request) {
|
|
|
2261
2682
|
message: request.resultMessage
|
|
2262
2683
|
};
|
|
2263
2684
|
}
|
|
2264
|
-
|
|
2685
|
+
let lockConflictMessage = null;
|
|
2686
|
+
const release = await acquireWriterLocks({
|
|
2687
|
+
rootPath,
|
|
2688
|
+
targets: getWriterLockTargetsForRequest(request),
|
|
2689
|
+
requestId: request.requestId
|
|
2690
|
+
}).catch(async (error) => {
|
|
2691
|
+
if (error instanceof WriterLockConflictError) {
|
|
2692
|
+
lockConflictMessage = error.message;
|
|
2693
|
+
await updateRequestRecord(rootPath, request, {
|
|
2694
|
+
requestState: "recovery_required",
|
|
2695
|
+
processedAt: Date.now(),
|
|
2696
|
+
resultMessage: error.message
|
|
2697
|
+
});
|
|
2698
|
+
return null;
|
|
2699
|
+
}
|
|
2700
|
+
throw error;
|
|
2701
|
+
});
|
|
2702
|
+
if (!release) {
|
|
2703
|
+
return {
|
|
2704
|
+
outcome: "recovery_required",
|
|
2705
|
+
requestId: request.requestId,
|
|
2706
|
+
taskId: "taskId" in request ? request.taskId : null,
|
|
2707
|
+
message: lockConflictMessage
|
|
2708
|
+
};
|
|
2709
|
+
}
|
|
2265
2710
|
try {
|
|
2266
2711
|
if (request.requestType === "replace_goal") {
|
|
2267
2712
|
return await applyReplaceGoalRequest(rootPath, request);
|
|
2268
2713
|
}
|
|
2714
|
+
if (request.requestType === "upsert_agent") {
|
|
2715
|
+
return await applyUpsertAgentRequest(rootPath, request);
|
|
2716
|
+
}
|
|
2269
2717
|
if (request.requestType === "create_task") {
|
|
2270
2718
|
return await applyCreateTaskRequest(rootPath, request);
|
|
2271
2719
|
}
|
|
@@ -2306,99 +2754,125 @@ function nextStatusFromTurnReport(report) {
|
|
|
2306
2754
|
return "active";
|
|
2307
2755
|
}
|
|
2308
2756
|
async function recordHappyOrgTurnReport(options) {
|
|
2309
|
-
const
|
|
2310
|
-
|
|
2757
|
+
const requestId = `turn-report:${options.report.taskId}`;
|
|
2758
|
+
let lockConflictMessage = null;
|
|
2759
|
+
const release = await acquireWriterLocks({
|
|
2760
|
+
rootPath: options.rootPath,
|
|
2761
|
+
targets: getWriterLockTargetsForTurnReport(options.report.taskId),
|
|
2762
|
+
requestId
|
|
2763
|
+
}).catch((error) => {
|
|
2764
|
+
if (error instanceof WriterLockConflictError) {
|
|
2765
|
+
lockConflictMessage = error.message;
|
|
2766
|
+
return null;
|
|
2767
|
+
}
|
|
2768
|
+
throw error;
|
|
2769
|
+
});
|
|
2770
|
+
if (!release) {
|
|
2311
2771
|
return {
|
|
2312
2772
|
outcome: "recovery_required",
|
|
2313
|
-
requestId
|
|
2773
|
+
requestId,
|
|
2314
2774
|
taskId: options.report.taskId,
|
|
2315
|
-
message: `
|
|
2775
|
+
message: lockConflictMessage ?? `Turn report blocked by writer lock for ${options.report.taskId}`
|
|
2316
2776
|
};
|
|
2317
2777
|
}
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
portablePath: normalizePortablePath(options.report.targetArtifact ?? bundle.task.value.path)
|
|
2333
|
-
};
|
|
2334
|
-
await writeEnvelope(path.join(buildTaskRoot(options.rootPath, options.report.taskId), "TASK.json"), {
|
|
2335
|
-
...bundle.task,
|
|
2336
|
-
revision: bundle.task.revision + 1,
|
|
2337
|
-
updatedAt: now,
|
|
2338
|
-
updatedBy: options.report.memberAgentId,
|
|
2339
|
-
value: nextTaskValue
|
|
2340
|
-
});
|
|
2341
|
-
await writeEnvelope(path.join(buildTaskRoot(options.rootPath, options.report.taskId), "WORKFLOW.json"), {
|
|
2342
|
-
schemaVersion: HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION,
|
|
2343
|
-
revision: (bundle.workflow?.revision ?? 0) + 1,
|
|
2344
|
-
updatedAt: now,
|
|
2345
|
-
updatedBy: options.report.memberAgentId,
|
|
2346
|
-
value: {
|
|
2347
|
-
taskId: options.report.taskId,
|
|
2778
|
+
try {
|
|
2779
|
+
const bundle = await loadTaskEnvelope(options.rootPath, options.report.taskId);
|
|
2780
|
+
if (!bundle) {
|
|
2781
|
+
return {
|
|
2782
|
+
outcome: "recovery_required",
|
|
2783
|
+
requestId,
|
|
2784
|
+
taskId: options.report.taskId,
|
|
2785
|
+
message: `Task ${options.report.taskId} not found`
|
|
2786
|
+
};
|
|
2787
|
+
}
|
|
2788
|
+
const now = Date.now();
|
|
2789
|
+
const nextStatus = nextStatusFromTurnReport(options.report);
|
|
2790
|
+
const nextTaskValue = {
|
|
2791
|
+
...bundle.task.value,
|
|
2348
2792
|
status: nextStatus,
|
|
2349
2793
|
summary: options.report.summary,
|
|
2350
|
-
|
|
2794
|
+
resultSummary: options.report.turnStatus === "task_complete" ? options.report.summary : bundle.task.value.resultSummary,
|
|
2795
|
+
blocker: nextStatus === "blocked" ? options.report.summary || options.report.blockerCode : null,
|
|
2351
2796
|
decisionNeeded: nextStatus === "waiting_decision" ? options.report.decisionNeeded ?? options.report.summary : null,
|
|
2352
|
-
adviceRoute: null,
|
|
2353
|
-
adviceSummary: null,
|
|
2354
|
-
lastRequestId: null,
|
|
2355
2797
|
updatedAt: now,
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
resultSummary: options.report.turnStatus === "task_complete" ? options.report.summary : bundle.result?.value.resultSummary ?? bundle.task.value.resultSummary,
|
|
2367
|
-
reviewSummary: bundle.result?.value.reviewSummary ?? null,
|
|
2368
|
-
assetCaptureStatus: bundle.result?.value.assetCaptureStatus ?? null,
|
|
2369
|
-
assetCaptureSummary: bundle.result?.value.assetCaptureSummary ?? null,
|
|
2370
|
-
assetReference: options.report.targetArtifact ?? bundle.result?.value.assetReference ?? null,
|
|
2371
|
-
closedAt: nextStatus === "waiting_close" ? now : bundle.result?.value.closedAt ?? null,
|
|
2798
|
+
lastUpdatedBy: options.report.memberAgentId,
|
|
2799
|
+
activeOwnerAgentId: options.report.memberAgentId,
|
|
2800
|
+
hasActiveOwner: nextStatus === "active",
|
|
2801
|
+
path: options.report.targetArtifact ?? bundle.task.value.path,
|
|
2802
|
+
portablePath: normalizePortablePath(options.report.targetArtifact ?? bundle.task.value.path)
|
|
2803
|
+
};
|
|
2804
|
+
const taskRoot = buildTaskRoot(options.rootPath, options.report.taskId);
|
|
2805
|
+
await writeEnvelope(path.join(taskRoot, "TASK.json"), {
|
|
2806
|
+
...bundle.task,
|
|
2807
|
+
revision: bundle.task.revision + 1,
|
|
2372
2808
|
updatedAt: now,
|
|
2373
|
-
updatedBy: options.report.memberAgentId
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2809
|
+
updatedBy: options.report.memberAgentId,
|
|
2810
|
+
value: nextTaskValue
|
|
2811
|
+
});
|
|
2812
|
+
await writeEnvelope(path.join(taskRoot, "WORKFLOW.json"), {
|
|
2813
|
+
schemaVersion: HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION,
|
|
2814
|
+
revision: (bundle.workflow?.revision ?? 0) + 1,
|
|
2815
|
+
updatedAt: now,
|
|
2816
|
+
updatedBy: options.report.memberAgentId,
|
|
2817
|
+
value: {
|
|
2818
|
+
taskId: options.report.taskId,
|
|
2819
|
+
status: nextStatus,
|
|
2820
|
+
summary: options.report.summary,
|
|
2821
|
+
blocker: nextStatus === "blocked" ? options.report.blockerCode ?? options.report.summary : null,
|
|
2822
|
+
decisionNeeded: nextStatus === "waiting_decision" ? options.report.decisionNeeded ?? options.report.summary : null,
|
|
2823
|
+
adviceRoute: null,
|
|
2824
|
+
adviceSummary: null,
|
|
2825
|
+
lastRequestId: null,
|
|
2826
|
+
updatedAt: now,
|
|
2827
|
+
updatedBy: options.report.memberAgentId
|
|
2828
|
+
}
|
|
2829
|
+
});
|
|
2830
|
+
await writeEnvelope(path.join(taskRoot, "RESULT.json"), {
|
|
2831
|
+
schemaVersion: HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION,
|
|
2832
|
+
revision: (bundle.result?.revision ?? 0) + 1,
|
|
2833
|
+
updatedAt: now,
|
|
2834
|
+
updatedBy: options.report.memberAgentId,
|
|
2835
|
+
value: {
|
|
2836
|
+
taskId: options.report.taskId,
|
|
2837
|
+
resultSummary: options.report.turnStatus === "task_complete" ? options.report.summary : bundle.result?.value.resultSummary ?? bundle.task.value.resultSummary,
|
|
2838
|
+
reviewSummary: bundle.result?.value.reviewSummary ?? null,
|
|
2839
|
+
assetCaptureStatus: bundle.result?.value.assetCaptureStatus ?? null,
|
|
2840
|
+
assetCaptureSummary: bundle.result?.value.assetCaptureSummary ?? null,
|
|
2841
|
+
assetReference: options.report.targetArtifact ?? bundle.result?.value.assetReference ?? null,
|
|
2842
|
+
closedAt: nextStatus === "waiting_close" ? now : bundle.result?.value.closedAt ?? null,
|
|
2843
|
+
updatedAt: now,
|
|
2844
|
+
updatedBy: options.report.memberAgentId
|
|
2845
|
+
}
|
|
2846
|
+
});
|
|
2847
|
+
await writeEnvelope(path.join(taskRoot, "LOG.json"), {
|
|
2848
|
+
schemaVersion: HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION,
|
|
2849
|
+
revision: (bundle.log?.revision ?? 0) + 1,
|
|
2850
|
+
updatedAt: now,
|
|
2851
|
+
updatedBy: options.report.memberAgentId,
|
|
2852
|
+
value: {
|
|
2853
|
+
taskId: options.report.taskId,
|
|
2854
|
+
entries: [
|
|
2855
|
+
...bundle.log?.value.entries ?? [],
|
|
2856
|
+
buildTaskLogEntry(
|
|
2857
|
+
"turn_report",
|
|
2858
|
+
`Recorded ${options.report.turnStatus} from ${options.report.memberAgentId}`,
|
|
2859
|
+
options.report.summary,
|
|
2860
|
+
options.report.memberAgentId,
|
|
2861
|
+
now
|
|
2862
|
+
)
|
|
2863
|
+
]
|
|
2864
|
+
}
|
|
2865
|
+
});
|
|
2866
|
+
await rebuildResponsibilities(options.rootPath);
|
|
2867
|
+
return {
|
|
2868
|
+
outcome: "applied",
|
|
2869
|
+
requestId,
|
|
2382
2870
|
taskId: options.report.taskId,
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
options.report.summary,
|
|
2389
|
-
options.report.memberAgentId,
|
|
2390
|
-
now
|
|
2391
|
-
)
|
|
2392
|
-
]
|
|
2393
|
-
}
|
|
2394
|
-
});
|
|
2395
|
-
await rebuildResponsibilities(options.rootPath);
|
|
2396
|
-
return {
|
|
2397
|
-
outcome: "applied",
|
|
2398
|
-
requestId: `turn-report:${options.report.taskId}`,
|
|
2399
|
-
taskId: options.report.taskId,
|
|
2400
|
-
message: `recorded turn report for ${options.report.taskId}`
|
|
2401
|
-
};
|
|
2871
|
+
message: `recorded turn report for ${options.report.taskId}`
|
|
2872
|
+
};
|
|
2873
|
+
} finally {
|
|
2874
|
+
await release();
|
|
2875
|
+
}
|
|
2402
2876
|
}
|
|
2403
2877
|
|
|
2404
2878
|
function buildSessionRuntimeIndex(metadata) {
|
|
@@ -3346,6 +3820,9 @@ const PROTOCOL_V3_CHANGES_PAGE_LIMIT = 200;
|
|
|
3346
3820
|
const PROTOCOL_V3_CAPABILITIES_WAIT_MS = 250;
|
|
3347
3821
|
const COMMITTED_SESSION_WRITE_ACK_TIMEOUT_MS = 5e3;
|
|
3348
3822
|
const RELIABLE_SESSION_MESSAGE_RETRY_DELAY_MS = 250;
|
|
3823
|
+
const LEGACY_CONTROL_ACK_HEADER$1 = Object.freeze({
|
|
3824
|
+
"x-happy-legacy-control-ack": "happy-org-1.7.3-legacy-write"
|
|
3825
|
+
});
|
|
3349
3826
|
function normalizeHappyOrgOptionalText(value) {
|
|
3350
3827
|
if (typeof value !== "string") {
|
|
3351
3828
|
return null;
|
|
@@ -3383,6 +3860,12 @@ function buildHappyOrgTurnReportSubmissionKey(messageId, report) {
|
|
|
3383
3860
|
report.summary.trim()
|
|
3384
3861
|
].join(":");
|
|
3385
3862
|
}
|
|
3863
|
+
function withLegacyControlAck$1(headers) {
|
|
3864
|
+
return {
|
|
3865
|
+
...headers ?? {},
|
|
3866
|
+
...LEGACY_CONTROL_ACK_HEADER$1
|
|
3867
|
+
};
|
|
3868
|
+
}
|
|
3386
3869
|
function parseHappyOrgFallbackEnvelope(rawEnvelope) {
|
|
3387
3870
|
try {
|
|
3388
3871
|
const parsed = JSON.parse(rawEnvelope);
|
|
@@ -3403,7 +3886,9 @@ function parseHappyOrgFallbackEnvelope(rawEnvelope) {
|
|
|
3403
3886
|
replyTo,
|
|
3404
3887
|
organizationId: typeof parsed.organization_id === "string" ? parsed.organization_id.trim() : null,
|
|
3405
3888
|
memberAgentId: typeof parsed.member_agent_id === "string" ? parsed.member_agent_id.trim() : null,
|
|
3406
|
-
supervisorAgentId: typeof parsed.supervisor_agent_id === "string" ? parsed.supervisor_agent_id.trim() : null
|
|
3889
|
+
supervisorAgentId: typeof parsed.supervisor_agent_id === "string" ? parsed.supervisor_agent_id.trim() : null,
|
|
3890
|
+
positionId: typeof parsed.position_id === "string" ? parsed.position_id.trim() : null,
|
|
3891
|
+
responsibilityId: typeof parsed.responsibility_id === "string" ? parsed.responsibility_id.trim() : null
|
|
3407
3892
|
};
|
|
3408
3893
|
} catch {
|
|
3409
3894
|
return null;
|
|
@@ -3420,13 +3905,17 @@ function buildHappyOrgFallbackUserMessage(rawEnvelope) {
|
|
|
3420
3905
|
taskId: envelope.taskId,
|
|
3421
3906
|
organizationId: envelope.organizationId,
|
|
3422
3907
|
memberAgentId: envelope.memberAgentId,
|
|
3423
|
-
supervisorAgentId: envelope.supervisorAgentId
|
|
3908
|
+
supervisorAgentId: envelope.supervisorAgentId,
|
|
3909
|
+
...envelope.positionId ? { positionId: envelope.positionId } : {},
|
|
3910
|
+
...envelope.responsibilityId ? { responsibilityId: envelope.responsibilityId } : {}
|
|
3424
3911
|
}
|
|
3425
3912
|
} : {},
|
|
3426
3913
|
replyContext: {
|
|
3427
3914
|
dispatchId: envelope.dispatchId,
|
|
3428
3915
|
scope: envelope.scope,
|
|
3429
|
-
replyTo: envelope.replyTo
|
|
3916
|
+
replyTo: envelope.replyTo,
|
|
3917
|
+
...envelope.positionId ? { positionId: envelope.positionId } : {},
|
|
3918
|
+
...envelope.responsibilityId ? { responsibilityId: envelope.responsibilityId } : {}
|
|
3430
3919
|
}
|
|
3431
3920
|
};
|
|
3432
3921
|
return {
|
|
@@ -4215,10 +4704,10 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
4215
4704
|
url,
|
|
4216
4705
|
body: payload,
|
|
4217
4706
|
signRequest: true,
|
|
4218
|
-
headers: {
|
|
4707
|
+
headers: withLegacyControlAck$1({
|
|
4219
4708
|
"Content-Type": "application/json",
|
|
4220
4709
|
"x-happy-organization-id": report.organizationId
|
|
4221
|
-
}
|
|
4710
|
+
})
|
|
4222
4711
|
}),
|
|
4223
4712
|
timeout: 1e4
|
|
4224
4713
|
});
|
|
@@ -4318,10 +4807,10 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
4318
4807
|
url,
|
|
4319
4808
|
body: payload,
|
|
4320
4809
|
signRequest: true,
|
|
4321
|
-
headers: {
|
|
4810
|
+
headers: withLegacyControlAck$1({
|
|
4322
4811
|
"Content-Type": "application/json",
|
|
4323
4812
|
"x-happy-organization-id": candidate.organizationId
|
|
4324
|
-
}
|
|
4813
|
+
})
|
|
4325
4814
|
}),
|
|
4326
4815
|
timeout: 1e4
|
|
4327
4816
|
});
|
|
@@ -5560,6 +6049,15 @@ async function deriveKey(master, usage, path) {
|
|
|
5560
6049
|
}
|
|
5561
6050
|
|
|
5562
6051
|
const SESSION_DATA_KEY_USAGE = "Happy Session Data Key";
|
|
6052
|
+
const LEGACY_CONTROL_ACK_HEADER = Object.freeze({
|
|
6053
|
+
"x-happy-legacy-control-ack": "happy-org-1.7.3-legacy-write"
|
|
6054
|
+
});
|
|
6055
|
+
function withLegacyControlAck(headers) {
|
|
6056
|
+
return {
|
|
6057
|
+
...headers ?? {},
|
|
6058
|
+
...LEGACY_CONTROL_ACK_HEADER
|
|
6059
|
+
};
|
|
6060
|
+
}
|
|
5563
6061
|
class ApiClient {
|
|
5564
6062
|
static async create(credential) {
|
|
5565
6063
|
return new ApiClient(credential);
|
|
@@ -5656,9 +6154,11 @@ class ApiClient {
|
|
|
5656
6154
|
allowed_paths: opts.allowedPaths,
|
|
5657
6155
|
forbidden_paths: opts.forbiddenPaths ?? [],
|
|
5658
6156
|
cross_scope_policy: "ask-first",
|
|
6157
|
+
position_id: opts.positionId ?? null,
|
|
6158
|
+
responsibility_id: opts.responsibilityId ?? null,
|
|
5659
6159
|
note: opts.note ?? null
|
|
5660
6160
|
},
|
|
5661
|
-
headers: { "x-happy-organization-id": opts.organizationId },
|
|
6161
|
+
headers: withLegacyControlAck({ "x-happy-organization-id": opts.organizationId }),
|
|
5662
6162
|
timeout: 1e4
|
|
5663
6163
|
});
|
|
5664
6164
|
return response.data.receipt;
|
|
@@ -5684,7 +6184,7 @@ class ApiClient {
|
|
|
5684
6184
|
status: opts.status,
|
|
5685
6185
|
note: opts.note ?? null
|
|
5686
6186
|
},
|
|
5687
|
-
headers: { "x-happy-organization-id": opts.organizationId },
|
|
6187
|
+
headers: withLegacyControlAck({ "x-happy-organization-id": opts.organizationId }),
|
|
5688
6188
|
timeout: 1e4
|
|
5689
6189
|
});
|
|
5690
6190
|
return response.data.receipt;
|