happy-imou-cloud 2.1.26 → 2.1.28

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 (25) hide show
  1. package/dist/{BaseReasoningProcessor-0e-Wwv5i.cjs → BaseReasoningProcessor-AsSwxX2U.cjs} +4 -3
  2. package/dist/{BaseReasoningProcessor-KMtgV6ap.mjs → BaseReasoningProcessor-DYVFvY85.mjs} +4 -3
  3. package/dist/{ProviderSelectionHandler-CzRyfT1v.cjs → ProviderSelectionHandler-BKfo21qx.cjs} +2 -2
  4. package/dist/{ProviderSelectionHandler-CG8ktb5b.mjs → ProviderSelectionHandler-Cd_vN8wA.mjs} +2 -2
  5. package/dist/{api-hgzFUi7o.cjs → api-ChV_1TP7.cjs} +922 -12
  6. package/dist/{api-DugHuNd4.mjs → api-DYS9sGJr.mjs} +924 -17
  7. package/dist/{command-DU0KWNsf.mjs → command-Bx9UY5D5.mjs} +3 -3
  8. package/dist/{command-Dh8sawXu.cjs → command-ErrXrwTw.cjs} +3 -3
  9. package/dist/{index-ythl_OD6.cjs → index-CDyeCS7U.cjs} +322 -50
  10. package/dist/{index-hj-qbq8Y.mjs → index-DtlrIihs.mjs} +319 -47
  11. package/dist/index.cjs +2 -2
  12. package/dist/index.mjs +2 -2
  13. package/dist/lib.cjs +2 -2
  14. package/dist/lib.d.cts +5 -0
  15. package/dist/lib.d.mts +5 -0
  16. package/dist/lib.mjs +2 -2
  17. package/dist/{registerKillSessionHandler-Q_rOuCNA.cjs → registerKillSessionHandler-CBuJR_D8.cjs} +60 -6
  18. package/dist/{registerKillSessionHandler-BNzbdofF.mjs → registerKillSessionHandler-IlzXqONk.mjs} +60 -7
  19. package/dist/{runClaude-BUi2fgRI.cjs → runClaude-D_YCDVmM.cjs} +7 -6
  20. package/dist/{runClaude-Y84RT6V0.mjs → runClaude-VlpAUO9C.mjs} +7 -6
  21. package/dist/{runCodex-D2xIzHke.mjs → runCodex-CL1yQHxl.mjs} +15 -10
  22. package/dist/{runCodex-vO3-iZ8E.cjs → runCodex-jisfibyM.cjs} +15 -10
  23. package/dist/{runGemini-DIKiIVdN.cjs → runGemini-BtBrzhkF.cjs} +13 -8
  24. package/dist/{runGemini-CAotw19V.mjs → runGemini-EPv-yxY4.mjs} +13 -8
  25. package/package.json +1 -1
@@ -8,6 +8,7 @@ var os = require('node:os');
8
8
  var path = require('node:path');
9
9
  var z = require('zod');
10
10
  var node_events = require('node:events');
11
+ var promises = require('node:fs/promises');
11
12
  var socket_ioClient = require('socket.io-client');
12
13
  var node_crypto = require('node:crypto');
13
14
  var tweetnacl = require('tweetnacl');
@@ -15,7 +16,6 @@ var fs$2 = require('fs/promises');
15
16
  var crypto = require('crypto');
16
17
  var path$1 = require('path');
17
18
  var node_child_process = require('node:child_process');
18
- var promises = require('node:fs/promises');
19
19
  var expoServerSdk = require('expo-server-sdk');
20
20
 
21
21
  function _interopNamespaceDefault(e) {
@@ -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.26";
41
+ var version = "2.1.28";
42
42
  var description = "hicloud - Imou 企业定制版。关键是 happy!移动端远程 AI 编程工具,支持 Claude Code、Codex 和 Gemini CLI";
43
43
  var author = "long.zhu";
44
44
  var license = "MIT";
@@ -310,6 +310,39 @@ function getSessionLogPath() {
310
310
  const filename = configuration.isDaemonProcess ? `${timestamp}-daemon.log` : `${timestamp}.log`;
311
311
  return path.join(configuration.logsDir, filename);
312
312
  }
313
+ function isErrorLike(value) {
314
+ return value !== null && typeof value === "object" && ("stack" in value || "message" in value);
315
+ }
316
+ function formatLogArgument(value, pretty = false) {
317
+ if (typeof value === "string") {
318
+ return value;
319
+ }
320
+ if (value instanceof Error) {
321
+ return value.stack || value.message;
322
+ }
323
+ if (isErrorLike(value)) {
324
+ if (typeof value.stack === "string" && value.stack.length > 0) {
325
+ return value.stack;
326
+ }
327
+ if (typeof value.message === "string" && value.message.length > 0) {
328
+ return value.message;
329
+ }
330
+ }
331
+ try {
332
+ const serialized = JSON.stringify(value, null, pretty ? 2 : void 0);
333
+ return serialized === void 0 ? String(value) : serialized;
334
+ } catch {
335
+ if (isErrorLike(value)) {
336
+ if (typeof value.stack === "string" && value.stack.length > 0) {
337
+ return value.stack;
338
+ }
339
+ if (typeof value.message === "string" && value.message.length > 0) {
340
+ return value.message;
341
+ }
342
+ }
343
+ return String(value);
344
+ }
345
+ }
313
346
  class Logger {
314
347
  constructor(logFilePath = getSessionLogPath()) {
315
348
  this.logFilePath = logFilePath;
@@ -331,12 +364,18 @@ class Logger {
331
364
  if (!process.env.DEBUG) {
332
365
  this.debug(`In production, skipping message inspection`);
333
366
  }
334
- const truncateStrings = (obj) => {
367
+ const truncateStrings = (obj, seen = /* @__PURE__ */ new WeakSet()) => {
335
368
  if (typeof obj === "string") {
336
369
  return obj.length > maxStringLength ? obj.substring(0, maxStringLength) + "... [truncated for logs]" : obj;
337
370
  }
371
+ if (obj && typeof obj === "object") {
372
+ if (seen.has(obj)) {
373
+ return "[Circular]";
374
+ }
375
+ seen.add(obj);
376
+ }
338
377
  if (Array.isArray(obj)) {
339
- const truncatedArray = obj.map((item) => truncateStrings(item)).slice(0, maxArrayLength);
378
+ const truncatedArray = obj.map((item) => truncateStrings(item, seen)).slice(0, maxArrayLength);
340
379
  if (obj.length > maxArrayLength) {
341
380
  truncatedArray.push(`... [truncated array for logs up to ${maxArrayLength} items]`);
342
381
  }
@@ -348,14 +387,14 @@ class Logger {
348
387
  if (key === "usage") {
349
388
  continue;
350
389
  }
351
- result[key] = truncateStrings(value);
390
+ result[key] = truncateStrings(value, seen);
352
391
  }
353
392
  return result;
354
393
  }
355
394
  return obj;
356
395
  };
357
396
  const truncatedObject = truncateStrings(object);
358
- const json = JSON.stringify(truncatedObject, null, 2);
397
+ const json = formatLogArgument(truncatedObject, true);
359
398
  this.logToFile(`[${this.localTimezoneTimestamp()}]`, message, "\n", json);
360
399
  }
361
400
  info(message, ...args) {
@@ -409,9 +448,7 @@ class Logger {
409
448
  body: JSON.stringify({
410
449
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
411
450
  level,
412
- message: `${message} ${args.map(
413
- (a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)
414
- ).join(" ")}`,
451
+ message: `${message} ${args.map((arg) => formatLogArgument(arg, true)).join(" ")}`,
415
452
  source: "cli",
416
453
  platform: process.platform
417
454
  })
@@ -420,9 +457,7 @@ class Logger {
420
457
  }
421
458
  }
422
459
  logToFile(prefix, message, ...args) {
423
- const logLine = `${prefix} ${message} ${args.map(
424
- (arg) => typeof arg === "string" ? arg : JSON.stringify(arg)
425
- ).join(" ")}
460
+ const logLine = `${prefix} ${message} ${args.map((arg) => formatLogArgument(arg)).join(" ")}
426
461
  `;
427
462
  if (this.dangerouslyUnencryptedServerLoggingUrl) {
428
463
  let level = "info";
@@ -1559,6 +1594,813 @@ const AgentMessageSchema = z.z.object({
1559
1594
  });
1560
1595
  const MessageContentSchema = z.z.union([UserMessageSchema, AgentMessageSchema]);
1561
1596
 
1597
+ const HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION = "1.7.3-local-repo-v1";
1598
+ const TaskStatusSchema = z.z.enum([
1599
+ "draft",
1600
+ "ready",
1601
+ "active",
1602
+ "waiting_review",
1603
+ "waiting_decision",
1604
+ "blocked",
1605
+ "waiting_close",
1606
+ "done",
1607
+ "terminated"
1608
+ ]);
1609
+ const AdviceRouteValueSchema = z.z.union([
1610
+ z.z.literal("gm"),
1611
+ z.z.literal("lead"),
1612
+ z.z.literal("user")
1613
+ ]);
1614
+ const AdviceRouteSchema = z.z.union([
1615
+ AdviceRouteValueSchema,
1616
+ z.z.null()
1617
+ ]);
1618
+ const GoalSchema = z.z.object({
1619
+ summary: z.z.string(),
1620
+ successCriteria: z.z.array(z.z.string()),
1621
+ boundaryNotes: z.z.array(z.z.string()),
1622
+ strategyNotes: z.z.array(z.z.string()),
1623
+ roadmapNotes: z.z.array(z.z.string()),
1624
+ decisions: z.z.array(z.z.string()),
1625
+ approvedAt: z.z.number().nullable(),
1626
+ approvedBy: z.z.string().nullable(),
1627
+ updatedAt: z.z.number()
1628
+ });
1629
+ const TaskUpdatePayloadSchema = z.z.object({
1630
+ taskId: z.z.string().min(1),
1631
+ status: TaskStatusSchema,
1632
+ summary: z.z.string().optional(),
1633
+ resultSummary: z.z.string().optional(),
1634
+ reviewSummary: z.z.string().nullable().optional(),
1635
+ blocker: z.z.string().nullable().optional(),
1636
+ decisionNeeded: z.z.string().nullable().optional(),
1637
+ adviceRoute: AdviceRouteSchema.optional(),
1638
+ adviceSummary: z.z.string().nullable().optional(),
1639
+ assetCaptureStatus: z.z.enum([
1640
+ "linked_existing_asset",
1641
+ "new_asset_candidate",
1642
+ "no_asset_needed",
1643
+ "asset_capture_failed"
1644
+ ]).nullable().optional(),
1645
+ assetCaptureSummary: z.z.string().nullable().optional(),
1646
+ assetReference: z.z.string().nullable().optional(),
1647
+ closedAt: z.z.number().nullable().optional(),
1648
+ lastUpdatedBy: z.z.string().nullable().optional()
1649
+ });
1650
+ const BaseRequestSchema = z.z.object({
1651
+ schemaVersion: z.z.literal(HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION),
1652
+ requestId: z.z.string().min(1),
1653
+ requestState: z.z.enum(["pending", "applied", "recovery_required"]),
1654
+ createdAt: z.z.number().int(),
1655
+ createdBy: z.z.literal("app"),
1656
+ organizationSlug: z.z.string().min(1),
1657
+ processedAt: z.z.number().nullable(),
1658
+ resultMessage: z.z.string().nullable()
1659
+ });
1660
+ const ReplaceGoalRequestSchema = BaseRequestSchema.extend({
1661
+ requestType: z.z.literal("replace_goal"),
1662
+ goal: GoalSchema,
1663
+ expectedRevision: z.z.number().nullable()
1664
+ });
1665
+ const CreateTaskRequestSchema = BaseRequestSchema.extend({
1666
+ requestType: z.z.literal("create_task"),
1667
+ ownerAgentId: z.z.string().min(1),
1668
+ ownerName: z.z.string().min(1),
1669
+ ownerSessionId: z.z.string().nullable(),
1670
+ title: z.z.string().min(1),
1671
+ path: z.z.string(),
1672
+ successCriteria: z.z.array(z.z.string()),
1673
+ summary: z.z.string().nullable()
1674
+ });
1675
+ const UpdateTaskRequestSchema = BaseRequestSchema.extend({
1676
+ requestType: z.z.literal("update_task"),
1677
+ taskId: z.z.string().min(1),
1678
+ expectedRevision: z.z.number().nullable(),
1679
+ nextState: TaskUpdatePayloadSchema
1680
+ });
1681
+ const RequestSchema = z.z.discriminatedUnion("requestType", [
1682
+ ReplaceGoalRequestSchema,
1683
+ CreateTaskRequestSchema,
1684
+ UpdateTaskRequestSchema
1685
+ ]);
1686
+ const RevisionEnvelopeSchema = (value) => z.z.object({
1687
+ schemaVersion: z.z.literal(HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION),
1688
+ revision: z.z.number().int().nonnegative(),
1689
+ updatedAt: z.z.number().int(),
1690
+ updatedBy: z.z.string().nullable(),
1691
+ value
1692
+ });
1693
+ const PositionValueSchema = z.z.object({
1694
+ positionId: z.z.string(),
1695
+ slug: z.z.string(),
1696
+ label: z.z.string(),
1697
+ role: z.z.string(),
1698
+ agentId: z.z.string(),
1699
+ agentName: z.z.string(),
1700
+ supervisorAgentId: z.z.string().nullable(),
1701
+ responsibilityIds: z.z.array(z.z.string()),
1702
+ occupancyStatus: z.z.enum(["occupied", "vacancy"]),
1703
+ truthPath: z.z.string(),
1704
+ createdAt: z.z.number(),
1705
+ updatedAt: z.z.number()
1706
+ });
1707
+ const ResponsibilityValueSchema = z.z.object({
1708
+ responsibilityId: z.z.string(),
1709
+ title: z.z.string(),
1710
+ summary: z.z.string().nullable(),
1711
+ positionId: z.z.string(),
1712
+ participantType: z.z.enum(["agent", "vacancy"]),
1713
+ status: z.z.enum(["vacancy", "assigned", "in_progress"]),
1714
+ memberAgentId: z.z.string().nullable(),
1715
+ memberName: z.z.string().nullable(),
1716
+ activeTaskId: z.z.string().nullable(),
1717
+ createdAt: z.z.number(),
1718
+ updatedAt: z.z.number()
1719
+ });
1720
+ const TaskValueSchema = z.z.object({
1721
+ taskId: z.z.string(),
1722
+ slug: z.z.string(),
1723
+ title: z.z.string(),
1724
+ ownerAgentId: z.z.string(),
1725
+ ownerName: z.z.string(),
1726
+ ownerSessionId: z.z.string().nullable(),
1727
+ responsibilityId: z.z.string().nullable(),
1728
+ responsibilityLabel: z.z.string().nullable(),
1729
+ activeOwnerAgentId: z.z.string().nullable(),
1730
+ hasActiveOwner: z.z.boolean(),
1731
+ path: z.z.string(),
1732
+ portablePath: z.z.string().nullable(),
1733
+ successCriteria: z.z.array(z.z.string()),
1734
+ status: TaskStatusSchema,
1735
+ summary: z.z.string(),
1736
+ resultSummary: z.z.string(),
1737
+ reviewSummary: z.z.string().nullable(),
1738
+ blocker: z.z.string().nullable(),
1739
+ decisionNeeded: z.z.string().nullable(),
1740
+ adviceRoute: AdviceRouteSchema,
1741
+ adviceSummary: z.z.string().nullable(),
1742
+ assetCaptureStatus: z.z.enum([
1743
+ "linked_existing_asset",
1744
+ "new_asset_candidate",
1745
+ "no_asset_needed",
1746
+ "asset_capture_failed"
1747
+ ]).nullable(),
1748
+ assetCaptureSummary: z.z.string().nullable(),
1749
+ assetReference: z.z.string().nullable(),
1750
+ closedAt: z.z.number().nullable(),
1751
+ conversationSessionId: z.z.string().nullable(),
1752
+ createdAt: z.z.number(),
1753
+ updatedAt: z.z.number(),
1754
+ lastUpdatedBy: z.z.string().nullable()
1755
+ });
1756
+ const WorkflowValueSchema = z.z.object({
1757
+ taskId: z.z.string(),
1758
+ status: TaskStatusSchema,
1759
+ summary: z.z.string(),
1760
+ blocker: z.z.string().nullable(),
1761
+ decisionNeeded: z.z.string().nullable(),
1762
+ adviceRoute: AdviceRouteSchema,
1763
+ adviceSummary: z.z.string().nullable(),
1764
+ lastRequestId: z.z.string().nullable(),
1765
+ updatedAt: z.z.number(),
1766
+ updatedBy: z.z.string().nullable()
1767
+ });
1768
+ const ResultValueSchema = z.z.object({
1769
+ taskId: z.z.string(),
1770
+ resultSummary: z.z.string(),
1771
+ reviewSummary: z.z.string().nullable(),
1772
+ assetCaptureStatus: z.z.enum([
1773
+ "linked_existing_asset",
1774
+ "new_asset_candidate",
1775
+ "no_asset_needed",
1776
+ "asset_capture_failed"
1777
+ ]).nullable(),
1778
+ assetCaptureSummary: z.z.string().nullable(),
1779
+ assetReference: z.z.string().nullable(),
1780
+ closedAt: z.z.number().nullable(),
1781
+ updatedAt: z.z.number(),
1782
+ updatedBy: z.z.string().nullable()
1783
+ });
1784
+ const LogEntrySchema = z.z.object({
1785
+ id: z.z.string(),
1786
+ eventType: z.z.enum(["snapshot_bootstrap", "request_applied", "turn_report"]),
1787
+ message: z.z.string(),
1788
+ summary: z.z.string().nullable(),
1789
+ createdAt: z.z.number(),
1790
+ actor: z.z.string().nullable()
1791
+ });
1792
+ const LogValueSchema = z.z.object({
1793
+ taskId: z.z.string(),
1794
+ entries: z.z.array(LogEntrySchema)
1795
+ });
1796
+ const GoalEnvelopeSchema = RevisionEnvelopeSchema(GoalSchema);
1797
+ const PositionEnvelopeSchema = RevisionEnvelopeSchema(PositionValueSchema);
1798
+ const ResponsibilityEnvelopeSchema = RevisionEnvelopeSchema(z.z.array(ResponsibilityValueSchema));
1799
+ const TaskEnvelopeSchema = RevisionEnvelopeSchema(TaskValueSchema);
1800
+ const WorkflowEnvelopeSchema = RevisionEnvelopeSchema(WorkflowValueSchema);
1801
+ const ResultEnvelopeSchema = RevisionEnvelopeSchema(ResultValueSchema);
1802
+ const LogEnvelopeSchema = RevisionEnvelopeSchema(LogValueSchema);
1803
+ function normalizePortablePath(value) {
1804
+ const trimmed = value?.trim();
1805
+ if (!trimmed) {
1806
+ return null;
1807
+ }
1808
+ return trimmed.replace(/\\/g, "/").replace(/\/{2,}/g, "/").replace(/\/$/, "");
1809
+ }
1810
+ function normalizeAdviceRoute(value) {
1811
+ return value === "gm" || value === "lead" || value === "user" ? value : null;
1812
+ }
1813
+ function buildResponsibilityId(slug) {
1814
+ return `responsibility-${slug}`;
1815
+ }
1816
+ function buildPositionId(slug) {
1817
+ return `position-${slug}`;
1818
+ }
1819
+ function toTaskSlug(taskId, title) {
1820
+ const slug = title.trim().toLowerCase().replace(/[^a-z0-9\u4e00-\u9fa5]+/g, "-").replace(/^-+|-+$/g, "");
1821
+ return `${taskId.toLowerCase()}-${slug || "task"}`;
1822
+ }
1823
+ function buildTaskLogEntry(eventType, message, summary, actor, createdAt) {
1824
+ return {
1825
+ id: `${eventType}-${createdAt.toString(36)}`,
1826
+ eventType,
1827
+ message,
1828
+ summary,
1829
+ createdAt,
1830
+ actor
1831
+ };
1832
+ }
1833
+ async function ensureDir(dirPath) {
1834
+ await promises.mkdir(dirPath, { recursive: true });
1835
+ }
1836
+ async function readJson(filePath) {
1837
+ try {
1838
+ const text = await promises.readFile(filePath, "utf8");
1839
+ return JSON.parse(text);
1840
+ } catch {
1841
+ return null;
1842
+ }
1843
+ }
1844
+ async function writeJson(filePath, value) {
1845
+ await ensureDir(path.dirname(filePath));
1846
+ await promises.writeFile(filePath, JSON.stringify(value, null, 2), "utf8");
1847
+ }
1848
+ async function readEnvelope(filePath, schema) {
1849
+ const value = await readJson(filePath);
1850
+ const parsed = schema.safeParse(value);
1851
+ return parsed.success ? parsed.data : null;
1852
+ }
1853
+ async function writeEnvelope(filePath, value) {
1854
+ await writeJson(filePath, value);
1855
+ }
1856
+ function buildRequestFilePath(rootPath, requestId) {
1857
+ return path.join(rootPath, "requests", `${requestId}.json`);
1858
+ }
1859
+ function buildTaskRoot(rootPath, taskId) {
1860
+ return path.join(rootPath, "tasks", taskId);
1861
+ }
1862
+ async function loadTaskEnvelope(rootPath, taskId) {
1863
+ const taskRoot = buildTaskRoot(rootPath, taskId);
1864
+ const task = await readEnvelope(path.join(taskRoot, "TASK.json"), TaskEnvelopeSchema);
1865
+ if (!task) {
1866
+ return null;
1867
+ }
1868
+ const workflow = await readEnvelope(path.join(taskRoot, "WORKFLOW.json"), WorkflowEnvelopeSchema);
1869
+ const result = await readEnvelope(path.join(taskRoot, "RESULT.json"), ResultEnvelopeSchema);
1870
+ const log = await readEnvelope(path.join(taskRoot, "LOG.json"), LogEnvelopeSchema);
1871
+ return { task, workflow, result, log };
1872
+ }
1873
+ async function listTaskIds(rootPath) {
1874
+ try {
1875
+ const entries = await promises.readdir(path.join(rootPath, "tasks"), { withFileTypes: true });
1876
+ return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
1877
+ } catch {
1878
+ return [];
1879
+ }
1880
+ }
1881
+ async function nextTaskId(rootPath) {
1882
+ const taskIds = await listTaskIds(rootPath);
1883
+ 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
+ return `T-${String(nextNumber).padStart(3, "0")}`;
1885
+ }
1886
+ async function listPositionEnvelopes(rootPath) {
1887
+ try {
1888
+ const entries = await promises.readdir(path.join(rootPath, "positions"), { withFileTypes: true });
1889
+ const envelopes = await Promise.all(entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => readEnvelope(path.join(rootPath, "positions", entry.name), PositionEnvelopeSchema)));
1890
+ return envelopes.filter((entry) => entry !== null);
1891
+ } catch {
1892
+ return [];
1893
+ }
1894
+ }
1895
+ async function readResponsibilities(rootPath) {
1896
+ return await readEnvelope(path.join(rootPath, "RESPONSIBILITIES.json"), ResponsibilityEnvelopeSchema);
1897
+ }
1898
+ function mergeTaskEnvelope(taskBundle) {
1899
+ const { task, workflow, result } = taskBundle;
1900
+ return {
1901
+ ...task.value,
1902
+ status: workflow?.value.status ?? task.value.status,
1903
+ summary: workflow?.value.summary ?? task.value.summary,
1904
+ blocker: workflow?.value.blocker ?? task.value.blocker,
1905
+ decisionNeeded: workflow?.value.decisionNeeded ?? task.value.decisionNeeded,
1906
+ adviceRoute: workflow?.value.adviceRoute ?? task.value.adviceRoute,
1907
+ adviceSummary: workflow?.value.adviceSummary ?? task.value.adviceSummary,
1908
+ resultSummary: result?.value.resultSummary ?? task.value.resultSummary,
1909
+ reviewSummary: result?.value.reviewSummary ?? task.value.reviewSummary,
1910
+ assetCaptureStatus: result?.value.assetCaptureStatus ?? task.value.assetCaptureStatus,
1911
+ assetCaptureSummary: result?.value.assetCaptureSummary ?? task.value.assetCaptureSummary,
1912
+ assetReference: result?.value.assetReference ?? task.value.assetReference,
1913
+ closedAt: result?.value.closedAt ?? task.value.closedAt,
1914
+ updatedAt: Math.max(task.value.updatedAt, workflow?.value.updatedAt ?? 0, result?.value.updatedAt ?? 0)
1915
+ };
1916
+ }
1917
+ async function rebuildResponsibilities(rootPath) {
1918
+ const positions = await listPositionEnvelopes(rootPath);
1919
+ const tasks = await Promise.all((await listTaskIds(rootPath)).map(async (taskId) => {
1920
+ const bundle = await loadTaskEnvelope(rootPath, taskId);
1921
+ return bundle ? mergeTaskEnvelope(bundle) : null;
1922
+ }));
1923
+ const normalizedTasks = tasks.filter((task) => task !== null);
1924
+ const existing = await readResponsibilities(rootPath);
1925
+ const previousRevision = existing?.revision ?? 0;
1926
+ const responsibilities = positions.map((position) => {
1927
+ 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;
1928
+ return {
1929
+ responsibilityId: buildResponsibilityId(position.value.slug),
1930
+ title: `${position.value.agentName} responsibility`,
1931
+ summary: activeTask?.summary ?? null,
1932
+ positionId: buildPositionId(position.value.slug),
1933
+ participantType: "agent",
1934
+ status: activeTask ? "in_progress" : "assigned",
1935
+ memberAgentId: position.value.agentId,
1936
+ memberName: position.value.agentName,
1937
+ activeTaskId: activeTask?.taskId ?? null,
1938
+ createdAt: position.value.createdAt,
1939
+ updatedAt: Math.max(position.value.updatedAt, activeTask?.updatedAt ?? 0)
1940
+ };
1941
+ });
1942
+ await writeEnvelope(path.join(rootPath, "RESPONSIBILITIES.json"), {
1943
+ schemaVersion: HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION,
1944
+ revision: previousRevision + 1,
1945
+ updatedAt: Math.max(...responsibilities.map((item) => item.updatedAt), 0),
1946
+ updatedBy: "cli-writer",
1947
+ value: responsibilities
1948
+ });
1949
+ }
1950
+ async function updateRequestRecord(rootPath, request, patch) {
1951
+ const nextRequest = {
1952
+ ...request,
1953
+ ...patch
1954
+ };
1955
+ await writeJson(buildRequestFilePath(rootPath, request.requestId), nextRequest);
1956
+ return nextRequest;
1957
+ }
1958
+ async function applyReplaceGoalRequest(rootPath, request) {
1959
+ const goalPath = path.join(rootPath, "GOAL.json");
1960
+ const goalEnvelope = await readEnvelope(goalPath, GoalEnvelopeSchema);
1961
+ if (!goalEnvelope) {
1962
+ await updateRequestRecord(rootPath, request, {
1963
+ requestState: "recovery_required",
1964
+ processedAt: Date.now(),
1965
+ resultMessage: "GOAL.json is missing or invalid"
1966
+ });
1967
+ return {
1968
+ outcome: "recovery_required",
1969
+ requestId: request.requestId,
1970
+ taskId: null,
1971
+ message: "GOAL.json is missing or invalid"
1972
+ };
1973
+ }
1974
+ if (request.expectedRevision !== null && goalEnvelope.revision !== request.expectedRevision) {
1975
+ const message = `Goal revision mismatch: expected ${request.expectedRevision}, got ${goalEnvelope.revision}`;
1976
+ await updateRequestRecord(rootPath, request, {
1977
+ requestState: "recovery_required",
1978
+ processedAt: Date.now(),
1979
+ resultMessage: message
1980
+ });
1981
+ return {
1982
+ outcome: "recovery_required",
1983
+ requestId: request.requestId,
1984
+ taskId: null,
1985
+ message
1986
+ };
1987
+ }
1988
+ await writeEnvelope(goalPath, {
1989
+ ...goalEnvelope,
1990
+ revision: goalEnvelope.revision + 1,
1991
+ updatedAt: request.goal.updatedAt,
1992
+ updatedBy: request.goal.approvedBy ?? "app",
1993
+ value: request.goal
1994
+ });
1995
+ await updateRequestRecord(rootPath, request, {
1996
+ requestState: "applied",
1997
+ processedAt: Date.now(),
1998
+ resultMessage: "goal updated"
1999
+ });
2000
+ return {
2001
+ outcome: "applied",
2002
+ requestId: request.requestId,
2003
+ taskId: null,
2004
+ message: "goal updated"
2005
+ };
2006
+ }
2007
+ async function applyCreateTaskRequest(rootPath, request) {
2008
+ const taskId = await nextTaskId(rootPath);
2009
+ const taskRoot = buildTaskRoot(rootPath, taskId);
2010
+ const createdAt = Date.now();
2011
+ const title = request.title.trim();
2012
+ const position = (await listPositionEnvelopes(rootPath)).find((entry) => entry.value.agentId === request.ownerAgentId) ?? null;
2013
+ const taskValue = {
2014
+ taskId,
2015
+ slug: toTaskSlug(taskId, title),
2016
+ title,
2017
+ ownerAgentId: request.ownerAgentId,
2018
+ ownerName: request.ownerName,
2019
+ ownerSessionId: request.ownerSessionId,
2020
+ responsibilityId: position ? buildResponsibilityId(position.value.slug) : null,
2021
+ responsibilityLabel: position ? `${position.value.agentName} responsibility` : `${request.ownerName} responsibility`,
2022
+ activeOwnerAgentId: null,
2023
+ hasActiveOwner: false,
2024
+ path: request.path,
2025
+ portablePath: normalizePortablePath(request.path),
2026
+ successCriteria: request.successCriteria,
2027
+ status: "ready",
2028
+ summary: request.summary ?? "Task created and ready for execution.",
2029
+ resultSummary: "",
2030
+ reviewSummary: null,
2031
+ blocker: null,
2032
+ decisionNeeded: null,
2033
+ adviceRoute: null,
2034
+ adviceSummary: null,
2035
+ assetCaptureStatus: null,
2036
+ assetCaptureSummary: null,
2037
+ assetReference: null,
2038
+ closedAt: null,
2039
+ conversationSessionId: request.ownerSessionId,
2040
+ createdAt,
2041
+ updatedAt: createdAt,
2042
+ lastUpdatedBy: request.ownerAgentId
2043
+ };
2044
+ await ensureDir(taskRoot);
2045
+ await writeEnvelope(path.join(taskRoot, "TASK.json"), {
2046
+ schemaVersion: HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION,
2047
+ revision: 1,
2048
+ updatedAt: createdAt,
2049
+ updatedBy: request.ownerAgentId,
2050
+ value: taskValue
2051
+ });
2052
+ await writeEnvelope(path.join(taskRoot, "WORKFLOW.json"), {
2053
+ schemaVersion: HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION,
2054
+ revision: 1,
2055
+ updatedAt: createdAt,
2056
+ updatedBy: request.ownerAgentId,
2057
+ value: {
2058
+ taskId,
2059
+ status: "ready",
2060
+ summary: taskValue.summary,
2061
+ blocker: null,
2062
+ decisionNeeded: null,
2063
+ adviceRoute: null,
2064
+ adviceSummary: null,
2065
+ lastRequestId: request.requestId,
2066
+ updatedAt: createdAt,
2067
+ updatedBy: request.ownerAgentId
2068
+ }
2069
+ });
2070
+ await writeEnvelope(path.join(taskRoot, "RESULT.json"), {
2071
+ schemaVersion: HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION,
2072
+ revision: 1,
2073
+ updatedAt: createdAt,
2074
+ updatedBy: request.ownerAgentId,
2075
+ value: {
2076
+ taskId,
2077
+ resultSummary: "",
2078
+ reviewSummary: null,
2079
+ assetCaptureStatus: null,
2080
+ assetCaptureSummary: null,
2081
+ assetReference: null,
2082
+ closedAt: null,
2083
+ updatedAt: createdAt,
2084
+ updatedBy: request.ownerAgentId
2085
+ }
2086
+ });
2087
+ await writeEnvelope(path.join(taskRoot, "LOG.json"), {
2088
+ schemaVersion: HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION,
2089
+ revision: 1,
2090
+ updatedAt: createdAt,
2091
+ updatedBy: request.ownerAgentId,
2092
+ value: {
2093
+ taskId,
2094
+ entries: [
2095
+ buildTaskLogEntry("request_applied", `Created task ${taskId}`, taskValue.summary, request.ownerAgentId, createdAt)
2096
+ ]
2097
+ }
2098
+ });
2099
+ await rebuildResponsibilities(rootPath);
2100
+ await updateRequestRecord(rootPath, request, {
2101
+ requestState: "applied",
2102
+ processedAt: Date.now(),
2103
+ resultMessage: `created ${taskId}`
2104
+ });
2105
+ return {
2106
+ outcome: "applied",
2107
+ requestId: request.requestId,
2108
+ taskId,
2109
+ message: `created ${taskId}`
2110
+ };
2111
+ }
2112
+ async function applyUpdateTaskRequest(rootPath, request) {
2113
+ const bundle = await loadTaskEnvelope(rootPath, request.taskId);
2114
+ if (!bundle) {
2115
+ const message = `Task ${request.taskId} not found`;
2116
+ await updateRequestRecord(rootPath, request, {
2117
+ requestState: "recovery_required",
2118
+ processedAt: Date.now(),
2119
+ resultMessage: message
2120
+ });
2121
+ return {
2122
+ outcome: "recovery_required",
2123
+ requestId: request.requestId,
2124
+ taskId: request.taskId,
2125
+ message
2126
+ };
2127
+ }
2128
+ if (request.expectedRevision !== null && bundle.task.revision !== request.expectedRevision) {
2129
+ const message = `Task revision mismatch: expected ${request.expectedRevision}, got ${bundle.task.revision}`;
2130
+ await updateRequestRecord(rootPath, request, {
2131
+ requestState: "recovery_required",
2132
+ processedAt: Date.now(),
2133
+ resultMessage: message
2134
+ });
2135
+ return {
2136
+ outcome: "recovery_required",
2137
+ requestId: request.requestId,
2138
+ taskId: request.taskId,
2139
+ message
2140
+ };
2141
+ }
2142
+ const now = Date.now();
2143
+ const nextAdviceRoute = request.nextState.adviceRoute === void 0 ? bundle.task.value.adviceRoute : normalizeAdviceRoute(request.nextState.adviceRoute);
2144
+ const nextTaskValue = {
2145
+ ...bundle.task.value,
2146
+ status: request.nextState.status,
2147
+ summary: request.nextState.summary ?? bundle.task.value.summary,
2148
+ resultSummary: request.nextState.resultSummary ?? bundle.task.value.resultSummary,
2149
+ reviewSummary: request.nextState.reviewSummary === void 0 ? bundle.task.value.reviewSummary : request.nextState.reviewSummary,
2150
+ blocker: request.nextState.blocker === void 0 ? bundle.task.value.blocker : request.nextState.blocker,
2151
+ decisionNeeded: request.nextState.decisionNeeded === void 0 ? bundle.task.value.decisionNeeded : request.nextState.decisionNeeded,
2152
+ adviceRoute: nextAdviceRoute,
2153
+ adviceSummary: request.nextState.adviceSummary === void 0 ? bundle.task.value.adviceSummary : request.nextState.adviceSummary,
2154
+ assetCaptureStatus: request.nextState.assetCaptureStatus === void 0 ? bundle.task.value.assetCaptureStatus : request.nextState.assetCaptureStatus,
2155
+ assetCaptureSummary: request.nextState.assetCaptureSummary === void 0 ? bundle.task.value.assetCaptureSummary : request.nextState.assetCaptureSummary,
2156
+ assetReference: request.nextState.assetReference === void 0 ? bundle.task.value.assetReference : request.nextState.assetReference,
2157
+ closedAt: request.nextState.closedAt === void 0 ? bundle.task.value.closedAt : request.nextState.closedAt,
2158
+ updatedAt: now,
2159
+ lastUpdatedBy: request.nextState.lastUpdatedBy ?? bundle.task.value.lastUpdatedBy
2160
+ };
2161
+ const nextWorkflowValue = {
2162
+ taskId: request.taskId,
2163
+ status: nextTaskValue.status,
2164
+ summary: nextTaskValue.summary,
2165
+ blocker: nextTaskValue.blocker,
2166
+ decisionNeeded: nextTaskValue.decisionNeeded,
2167
+ adviceRoute: nextAdviceRoute,
2168
+ adviceSummary: nextTaskValue.adviceSummary,
2169
+ lastRequestId: request.requestId,
2170
+ updatedAt: now,
2171
+ updatedBy: nextTaskValue.lastUpdatedBy
2172
+ };
2173
+ const nextResultValue = {
2174
+ taskId: request.taskId,
2175
+ resultSummary: nextTaskValue.resultSummary,
2176
+ reviewSummary: nextTaskValue.reviewSummary,
2177
+ assetCaptureStatus: nextTaskValue.assetCaptureStatus,
2178
+ assetCaptureSummary: nextTaskValue.assetCaptureSummary,
2179
+ assetReference: nextTaskValue.assetReference,
2180
+ closedAt: nextTaskValue.closedAt,
2181
+ updatedAt: now,
2182
+ updatedBy: nextTaskValue.lastUpdatedBy
2183
+ };
2184
+ const nextLogValue = {
2185
+ taskId: request.taskId,
2186
+ entries: [
2187
+ ...bundle.log?.value.entries ?? [],
2188
+ buildTaskLogEntry(
2189
+ "request_applied",
2190
+ `Applied ${request.nextState.status} via ${request.requestId}`,
2191
+ nextTaskValue.summary,
2192
+ nextTaskValue.lastUpdatedBy,
2193
+ now
2194
+ )
2195
+ ]
2196
+ };
2197
+ const taskRoot = buildTaskRoot(rootPath, request.taskId);
2198
+ await writeEnvelope(path.join(taskRoot, "TASK.json"), {
2199
+ ...bundle.task,
2200
+ revision: bundle.task.revision + 1,
2201
+ updatedAt: now,
2202
+ updatedBy: nextTaskValue.lastUpdatedBy,
2203
+ value: nextTaskValue
2204
+ });
2205
+ await writeEnvelope(path.join(taskRoot, "WORKFLOW.json"), {
2206
+ schemaVersion: HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION,
2207
+ revision: (bundle.workflow?.revision ?? 0) + 1,
2208
+ updatedAt: now,
2209
+ updatedBy: nextTaskValue.lastUpdatedBy,
2210
+ value: nextWorkflowValue
2211
+ });
2212
+ await writeEnvelope(path.join(taskRoot, "RESULT.json"), {
2213
+ schemaVersion: HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION,
2214
+ revision: (bundle.result?.revision ?? 0) + 1,
2215
+ updatedAt: now,
2216
+ updatedBy: nextTaskValue.lastUpdatedBy,
2217
+ value: nextResultValue
2218
+ });
2219
+ await writeEnvelope(path.join(taskRoot, "LOG.json"), {
2220
+ schemaVersion: HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION,
2221
+ revision: (bundle.log?.revision ?? 0) + 1,
2222
+ updatedAt: now,
2223
+ updatedBy: nextTaskValue.lastUpdatedBy,
2224
+ value: nextLogValue
2225
+ });
2226
+ await rebuildResponsibilities(rootPath);
2227
+ await updateRequestRecord(rootPath, request, {
2228
+ requestState: "applied",
2229
+ processedAt: now,
2230
+ resultMessage: `updated ${request.taskId}`
2231
+ });
2232
+ return {
2233
+ outcome: "applied",
2234
+ requestId: request.requestId,
2235
+ taskId: request.taskId,
2236
+ message: `updated ${request.taskId}`
2237
+ };
2238
+ }
2239
+ async function acquireWriterLock(rootPath) {
2240
+ const lockPath = path.join(rootPath, ".runtime", "writer.lock.json");
2241
+ await ensureDir(path.dirname(lockPath));
2242
+ const handle = await promises.open(lockPath, "wx");
2243
+ await handle.writeFile(JSON.stringify({
2244
+ acquiredAt: Date.now(),
2245
+ pid: process.pid
2246
+ }, null, 2), "utf8");
2247
+ await handle.close();
2248
+ return async () => {
2249
+ await promises.unlink(lockPath).catch(async () => {
2250
+ await promises.rm(lockPath, { force: true });
2251
+ });
2252
+ };
2253
+ }
2254
+ async function processSingleRequest(rootPath, request) {
2255
+ if (request.requestState === "applied") {
2256
+ const duplicateTaskId = request.requestType === "create_task" ? request.resultMessage?.match(/T-\d+/)?.[0] ?? null : "taskId" in request ? request.taskId : null;
2257
+ return {
2258
+ outcome: "duplicate",
2259
+ requestId: request.requestId,
2260
+ taskId: duplicateTaskId,
2261
+ message: request.resultMessage
2262
+ };
2263
+ }
2264
+ const release = await acquireWriterLock(rootPath);
2265
+ try {
2266
+ if (request.requestType === "replace_goal") {
2267
+ return await applyReplaceGoalRequest(rootPath, request);
2268
+ }
2269
+ if (request.requestType === "create_task") {
2270
+ return await applyCreateTaskRequest(rootPath, request);
2271
+ }
2272
+ return await applyUpdateTaskRequest(rootPath, request);
2273
+ } finally {
2274
+ await release();
2275
+ }
2276
+ }
2277
+ async function loadRequest(rootPath, requestId) {
2278
+ const payload = await readJson(buildRequestFilePath(rootPath, requestId));
2279
+ const parsed = RequestSchema.safeParse(payload);
2280
+ return parsed.success ? parsed.data : null;
2281
+ }
2282
+ async function processHappyOrgRepoRequests(options) {
2283
+ await ensureDir(path.join(options.rootPath, "requests"));
2284
+ const requestIds = options.requestId ? [options.requestId] : (await promises.readdir(path.join(options.rootPath, "requests"), { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => entry.name.replace(/\.json$/i, ""));
2285
+ const requests = await Promise.all(requestIds.map((requestId) => loadRequest(options.rootPath, requestId)));
2286
+ const orderedRequests = requests.filter((request) => request !== null).sort((left, right) => left.createdAt - right.createdAt || left.requestId.localeCompare(right.requestId));
2287
+ const results = [];
2288
+ for (const request of orderedRequests) {
2289
+ results.push(await processSingleRequest(options.rootPath, request));
2290
+ }
2291
+ return results;
2292
+ }
2293
+ function nextStatusFromTurnReport(report) {
2294
+ if (report.turnStatus === "task_complete") {
2295
+ return "waiting_close";
2296
+ }
2297
+ if (report.interventionType === "review_needed") {
2298
+ return "waiting_review";
2299
+ }
2300
+ if (report.interventionType === "decision_needed") {
2301
+ return "waiting_decision";
2302
+ }
2303
+ if (report.interventionType === "blocker") {
2304
+ return "blocked";
2305
+ }
2306
+ return "active";
2307
+ }
2308
+ async function recordHappyOrgTurnReport(options) {
2309
+ const bundle = await loadTaskEnvelope(options.rootPath, options.report.taskId);
2310
+ if (!bundle) {
2311
+ return {
2312
+ outcome: "recovery_required",
2313
+ requestId: `turn-report:${options.report.taskId}`,
2314
+ taskId: options.report.taskId,
2315
+ message: `Task ${options.report.taskId} not found`
2316
+ };
2317
+ }
2318
+ const now = Date.now();
2319
+ const nextStatus = nextStatusFromTurnReport(options.report);
2320
+ const nextTaskValue = {
2321
+ ...bundle.task.value,
2322
+ status: nextStatus,
2323
+ summary: options.report.summary,
2324
+ resultSummary: options.report.turnStatus === "task_complete" ? options.report.summary : bundle.task.value.resultSummary,
2325
+ blocker: nextStatus === "blocked" ? options.report.summary || options.report.blockerCode : null,
2326
+ decisionNeeded: nextStatus === "waiting_decision" ? options.report.decisionNeeded ?? options.report.summary : null,
2327
+ updatedAt: now,
2328
+ lastUpdatedBy: options.report.memberAgentId,
2329
+ activeOwnerAgentId: options.report.memberAgentId,
2330
+ hasActiveOwner: nextStatus === "active",
2331
+ path: options.report.targetArtifact ?? bundle.task.value.path,
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,
2348
+ status: nextStatus,
2349
+ summary: options.report.summary,
2350
+ blocker: nextStatus === "blocked" ? options.report.blockerCode ?? options.report.summary : null,
2351
+ decisionNeeded: nextStatus === "waiting_decision" ? options.report.decisionNeeded ?? options.report.summary : null,
2352
+ adviceRoute: null,
2353
+ adviceSummary: null,
2354
+ lastRequestId: null,
2355
+ updatedAt: now,
2356
+ updatedBy: options.report.memberAgentId
2357
+ }
2358
+ });
2359
+ await writeEnvelope(path.join(buildTaskRoot(options.rootPath, options.report.taskId), "RESULT.json"), {
2360
+ schemaVersion: HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION,
2361
+ revision: (bundle.result?.revision ?? 0) + 1,
2362
+ updatedAt: now,
2363
+ updatedBy: options.report.memberAgentId,
2364
+ value: {
2365
+ taskId: options.report.taskId,
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,
2372
+ updatedAt: now,
2373
+ updatedBy: options.report.memberAgentId
2374
+ }
2375
+ });
2376
+ await writeEnvelope(path.join(buildTaskRoot(options.rootPath, options.report.taskId), "LOG.json"), {
2377
+ schemaVersion: HAPPY_ORG_LOCAL_REPO_SCHEMA_VERSION,
2378
+ revision: (bundle.log?.revision ?? 0) + 1,
2379
+ updatedAt: now,
2380
+ updatedBy: options.report.memberAgentId,
2381
+ value: {
2382
+ taskId: options.report.taskId,
2383
+ entries: [
2384
+ ...bundle.log?.value.entries ?? [],
2385
+ buildTaskLogEntry(
2386
+ "turn_report",
2387
+ `Recorded ${options.report.turnStatus} from ${options.report.memberAgentId}`,
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
+ };
2402
+ }
2403
+
1562
2404
  function buildSessionRuntimeIndex(metadata) {
1563
2405
  if (!metadata) {
1564
2406
  return null;
@@ -2511,6 +3353,14 @@ function normalizeHappyOrgOptionalText(value) {
2511
3353
  const normalized = value.trim();
2512
3354
  return normalized.length > 0 ? normalized : null;
2513
3355
  }
3356
+ async function pathExists(targetPath) {
3357
+ try {
3358
+ await promises.access(targetPath);
3359
+ return true;
3360
+ } catch {
3361
+ return false;
3362
+ }
3363
+ }
2514
3364
  function deriveHappyOrgTurnReportRepeatFingerprint(report) {
2515
3365
  const repeatFingerprint = normalizeHappyOrgOptionalText(report.repeatFingerprint);
2516
3366
  if (repeatFingerprint) {
@@ -3337,6 +4187,7 @@ class ApiSessionClient extends node_events.EventEmitter {
3337
4187
  }
3338
4188
  }
3339
4189
  async submitHappyOrgTaskTurnReport(report, submissionKey) {
4190
+ await this.recordHappyOrgTaskTurnReportLocally(report, submissionKey);
3340
4191
  const credentials = await this.ensureHappyOrgControlCredentials();
3341
4192
  if (!credentials) {
3342
4193
  return;
@@ -3386,6 +4237,62 @@ class ApiSessionClient extends node_events.EventEmitter {
3386
4237
  });
3387
4238
  }
3388
4239
  }
4240
+ async recordHappyOrgTaskTurnReportLocally(report, submissionKey) {
4241
+ const rootPath = await this.resolveHappyOrgLocalRepoRoot(report);
4242
+ if (!rootPath) {
4243
+ return;
4244
+ }
4245
+ try {
4246
+ await recordHappyOrgTurnReport({
4247
+ rootPath,
4248
+ report
4249
+ });
4250
+ this.recordedHappyOrgTaskTurnReports.add(submissionKey);
4251
+ logger.debug("[API] Happy Org task turn report recorded into local repo automatically", {
4252
+ taskId: report.taskId,
4253
+ memberAgentId: report.memberAgentId,
4254
+ rootPath,
4255
+ turnStatus: report.turnStatus
4256
+ });
4257
+ } catch (error) {
4258
+ logger.debug("[API] Failed to record Happy Org task turn report into local repo automatically", {
4259
+ taskId: report.taskId,
4260
+ memberAgentId: report.memberAgentId,
4261
+ rootPath,
4262
+ turnStatus: report.turnStatus,
4263
+ error
4264
+ });
4265
+ }
4266
+ }
4267
+ async resolveHappyOrgLocalRepoRoot(report) {
4268
+ const candidatePaths = [
4269
+ normalizeHappyOrgOptionalText(report.specialistHome?.path ?? null),
4270
+ normalizeHappyOrgOptionalText(this.metadata?.path ?? null)
4271
+ ].filter((value) => Boolean(value));
4272
+ const visited = /* @__PURE__ */ new Set();
4273
+ for (const candidatePath of candidatePaths) {
4274
+ let currentPath = candidatePath;
4275
+ while (!visited.has(currentPath)) {
4276
+ visited.add(currentPath);
4277
+ if (await this.isHappyOrgLocalRepoRoot(currentPath, report.taskId)) {
4278
+ return currentPath;
4279
+ }
4280
+ const parentPath = path.dirname(currentPath);
4281
+ if (!parentPath || parentPath === currentPath) {
4282
+ break;
4283
+ }
4284
+ currentPath = parentPath;
4285
+ }
4286
+ }
4287
+ return null;
4288
+ }
4289
+ async isHappyOrgLocalRepoRoot(rootPath, taskId) {
4290
+ const hasTruthMarker = await pathExists(path.join(rootPath, "MIGRATION.json")) || await pathExists(path.join(rootPath, "ORGANIZATION.json"));
4291
+ if (!hasTruthMarker) {
4292
+ return false;
4293
+ }
4294
+ return await pathExists(path.join(rootPath, "tasks", taskId, "TASK.json"));
4295
+ }
3389
4296
  async submitHappyOrgDispatchBusinessAck(candidate) {
3390
4297
  const credentials = await this.ensureHappyOrgControlCredentials();
3391
4298
  if (!credentials) {
@@ -5176,6 +6083,7 @@ exports.HAPPY_CLOUD_DAEMON_PORT = HAPPY_CLOUD_DAEMON_PORT;
5176
6083
  exports.HAPPY_ORG_REPEAT_THRESHOLD = HAPPY_ORG_REPEAT_THRESHOLD;
5177
6084
  exports.HAPPY_ORG_SUMMARY_MAX_LENGTH = HAPPY_ORG_SUMMARY_MAX_LENGTH;
5178
6085
  exports.HAPPY_ORG_TURN_REPORT_TAG = HAPPY_ORG_TURN_REPORT_TAG;
6086
+ exports.HappyOrgTurnReportSchema = HappyOrgTurnReportSchema;
5179
6087
  exports.HeadTailPreviewBuffer = HeadTailPreviewBuffer;
5180
6088
  exports.MessageContentSchema = MessageContentSchema;
5181
6089
  exports.acquireDaemonLock = acquireDaemonLock;
@@ -5201,9 +6109,11 @@ exports.logger = logger;
5201
6109
  exports.packageJson = packageJson;
5202
6110
  exports.persistence = persistence;
5203
6111
  exports.preserveSessionRuntimeMetadata = preserveSessionRuntimeMetadata;
6112
+ exports.processHappyOrgRepoRequests = processHappyOrgRepoRequests;
5204
6113
  exports.readCredentials = readCredentials;
5205
6114
  exports.readDaemonState = readDaemonState;
5206
6115
  exports.readSettings = readSettings;
6116
+ exports.recordHappyOrgTurnReport = recordHappyOrgTurnReport;
5207
6117
  exports.releaseDaemonLock = releaseDaemonLock;
5208
6118
  exports.startOfflineReconnection = startOfflineReconnection;
5209
6119
  exports.updateSettings = updateSettings;