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.
Files changed (26) hide show
  1. package/dist/{BaseReasoningProcessor-DYVFvY85.mjs → BaseReasoningProcessor-C1iacoJW.mjs} +2 -2
  2. package/dist/{BaseReasoningProcessor-AsSwxX2U.cjs → BaseReasoningProcessor-Ch9R4qmn.cjs} +2 -2
  3. package/dist/{ProviderSelectionHandler-Cd_vN8wA.mjs → ProviderSelectionHandler-2siFKlgs.mjs} +2 -2
  4. package/dist/{ProviderSelectionHandler-BKfo21qx.cjs → ProviderSelectionHandler-Dx6x0Nd4.cjs} +2 -2
  5. package/dist/{api-DYS9sGJr.mjs → api-C68U-kRs.mjs} +626 -126
  6. package/dist/{api-ChV_1TP7.cjs → api-DK1gyZAZ.cjs} +626 -126
  7. package/dist/{command-ErrXrwTw.cjs → command-Cb9nikZh.cjs} +2 -2
  8. package/dist/{command-Bx9UY5D5.mjs → command-D32x08k9.mjs} +2 -2
  9. package/dist/{index-CDyeCS7U.cjs → index-BLeiCte-.cjs} +174 -32
  10. package/dist/{index-DtlrIihs.mjs → index-DQ76ZTNL.mjs} +171 -29
  11. package/dist/index.cjs +2 -2
  12. package/dist/index.mjs +2 -2
  13. package/dist/lib.cjs +1 -1
  14. package/dist/lib.d.cts +100 -0
  15. package/dist/lib.d.mts +100 -0
  16. package/dist/lib.mjs +1 -1
  17. package/dist/{registerKillSessionHandler-IlzXqONk.mjs → registerKillSessionHandler-CadrzRgP.mjs} +16 -6
  18. package/dist/{registerKillSessionHandler-CBuJR_D8.cjs → registerKillSessionHandler-DD9uUk4w.cjs} +16 -6
  19. package/dist/{runClaude-VlpAUO9C.mjs → runClaude-CkY6XYJa.mjs} +4 -4
  20. package/dist/{runClaude-D_YCDVmM.cjs → runClaude-U9sxsnU-.cjs} +4 -4
  21. package/dist/{runCodex-jisfibyM.cjs → runCodex-Beikmv-L.cjs} +134 -9
  22. package/dist/{runCodex-CL1yQHxl.mjs → runCodex-C6kV0jfX.mjs} +134 -9
  23. package/dist/{runGemini-BtBrzhkF.cjs → runGemini-BTyqf5MR.cjs} +4 -4
  24. package/dist/{runGemini-EPv-yxY4.mjs → runGemini-IEzJdhc-.mjs} +4 -4
  25. package/package.json +1 -1
  26. 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.28";
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
- agentId: z$1.string(),
1679
- agentName: z$1.string(),
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 buildPositionId(slug) {
1797
- return `position-${slug}`;
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 listPositionEnvelopes(rootPath) {
2048
+ async function listPositionEntries(rootPath) {
1867
2049
  try {
1868
2050
  const entries = await readdir(path.join(rootPath, "positions"), { withFileTypes: true });
1869
- const envelopes = await Promise.all(entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => readEnvelope(path.join(rootPath, "positions", entry.name), PositionEnvelopeSchema)));
1870
- return envelopes.filter((entry) => entry !== null);
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 activeTask = normalizedTasks.filter((task) => task.ownerAgentId === position.value.agentId && task.status !== "done" && task.status !== "terminated").sort((left, right) => right.updatedAt - left.updatedAt)[0] ?? null;
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: buildResponsibilityId(position.value.slug),
1910
- title: `${position.value.agentName} responsibility`,
2129
+ responsibilityId,
2130
+ title: position.value.label || position.value.agentName || `${position.value.slug} responsibility`,
1911
2131
  summary: activeTask?.summary ?? null,
1912
- positionId: buildPositionId(position.value.slug),
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: position ? buildResponsibilityId(position.value.slug) : null,
2001
- responsibilityLabel: position ? `${position.value.agentName} responsibility` : `${request.ownerName} responsibility`,
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 acquireWriterLock(rootPath) {
2220
- const lockPath = path.join(rootPath, ".runtime", "writer.lock.json");
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
- const handle = await open(lockPath, "wx");
2223
- await handle.writeFile(JSON.stringify({
2224
- acquiredAt: Date.now(),
2225
- pid: process.pid
2226
- }, null, 2), "utf8");
2227
- await handle.close();
2228
- return async () => {
2229
- await unlink(lockPath).catch(async () => {
2230
- await rm(lockPath, { force: true });
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
- const release = await acquireWriterLock(rootPath);
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 bundle = await loadTaskEnvelope(options.rootPath, options.report.taskId);
2290
- if (!bundle) {
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: `turn-report:${options.report.taskId}`,
2753
+ requestId,
2294
2754
  taskId: options.report.taskId,
2295
- message: `Task ${options.report.taskId} not found`
2755
+ message: lockConflictMessage ?? `Turn report blocked by writer lock for ${options.report.taskId}`
2296
2756
  };
2297
2757
  }
2298
- const now = Date.now();
2299
- const nextStatus = nextStatusFromTurnReport(options.report);
2300
- const nextTaskValue = {
2301
- ...bundle.task.value,
2302
- status: nextStatus,
2303
- summary: options.report.summary,
2304
- resultSummary: options.report.turnStatus === "task_complete" ? options.report.summary : bundle.task.value.resultSummary,
2305
- blocker: nextStatus === "blocked" ? options.report.summary || options.report.blockerCode : null,
2306
- decisionNeeded: nextStatus === "waiting_decision" ? options.report.decisionNeeded ?? options.report.summary : null,
2307
- updatedAt: now,
2308
- lastUpdatedBy: options.report.memberAgentId,
2309
- activeOwnerAgentId: options.report.memberAgentId,
2310
- hasActiveOwner: nextStatus === "active",
2311
- path: options.report.targetArtifact ?? bundle.task.value.path,
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
- blocker: nextStatus === "blocked" ? options.report.blockerCode ?? options.report.summary : null,
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
- updatedBy: options.report.memberAgentId
2337
- }
2338
- });
2339
- await writeEnvelope(path.join(buildTaskRoot(options.rootPath, options.report.taskId), "RESULT.json"), {
2340
- schemaVersion: HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION,
2341
- revision: (bundle.result?.revision ?? 0) + 1,
2342
- updatedAt: now,
2343
- updatedBy: options.report.memberAgentId,
2344
- value: {
2345
- taskId: options.report.taskId,
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
- await writeEnvelope(path.join(buildTaskRoot(options.rootPath, options.report.taskId), "LOG.json"), {
2357
- schemaVersion: HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION,
2358
- revision: (bundle.log?.revision ?? 0) + 1,
2359
- updatedAt: now,
2360
- updatedBy: options.report.memberAgentId,
2361
- value: {
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
- entries: [
2364
- ...bundle.log?.value.entries ?? [],
2365
- buildTaskLogEntry(
2366
- "turn_report",
2367
- `Recorded ${options.report.turnStatus} from ${options.report.memberAgentId}`,
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;