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.
- package/dist/{BaseReasoningProcessor-0e-Wwv5i.cjs → BaseReasoningProcessor-AsSwxX2U.cjs} +4 -3
- package/dist/{BaseReasoningProcessor-KMtgV6ap.mjs → BaseReasoningProcessor-DYVFvY85.mjs} +4 -3
- package/dist/{ProviderSelectionHandler-CzRyfT1v.cjs → ProviderSelectionHandler-BKfo21qx.cjs} +2 -2
- package/dist/{ProviderSelectionHandler-CG8ktb5b.mjs → ProviderSelectionHandler-Cd_vN8wA.mjs} +2 -2
- package/dist/{api-hgzFUi7o.cjs → api-ChV_1TP7.cjs} +922 -12
- package/dist/{api-DugHuNd4.mjs → api-DYS9sGJr.mjs} +924 -17
- package/dist/{command-DU0KWNsf.mjs → command-Bx9UY5D5.mjs} +3 -3
- package/dist/{command-Dh8sawXu.cjs → command-ErrXrwTw.cjs} +3 -3
- package/dist/{index-ythl_OD6.cjs → index-CDyeCS7U.cjs} +322 -50
- package/dist/{index-hj-qbq8Y.mjs → index-DtlrIihs.mjs} +319 -47
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/lib.cjs +2 -2
- package/dist/lib.d.cts +5 -0
- package/dist/lib.d.mts +5 -0
- package/dist/lib.mjs +2 -2
- package/dist/{registerKillSessionHandler-Q_rOuCNA.cjs → registerKillSessionHandler-CBuJR_D8.cjs} +60 -6
- package/dist/{registerKillSessionHandler-BNzbdofF.mjs → registerKillSessionHandler-IlzXqONk.mjs} +60 -7
- package/dist/{runClaude-BUi2fgRI.cjs → runClaude-D_YCDVmM.cjs} +7 -6
- package/dist/{runClaude-Y84RT6V0.mjs → runClaude-VlpAUO9C.mjs} +7 -6
- package/dist/{runCodex-D2xIzHke.mjs → runCodex-CL1yQHxl.mjs} +15 -10
- package/dist/{runCodex-vO3-iZ8E.cjs → runCodex-jisfibyM.cjs} +15 -10
- package/dist/{runGemini-DIKiIVdN.cjs → runGemini-BtBrzhkF.cjs} +13 -8
- package/dist/{runGemini-CAotw19V.mjs → runGemini-EPv-yxY4.mjs} +13 -8
- 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.
|
|
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 =
|
|
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;
|