@haaaiawd/second-nature 0.1.33 → 0.1.38

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 (39) hide show
  1. package/agent-inner-guide.md +25 -0
  2. package/index.js +1 -1
  3. package/openclaw.plugin.json +1 -1
  4. package/package.json +1 -1
  5. package/runtime/cli/commands/goal.d.ts +1 -0
  6. package/runtime/cli/commands/goal.js +1 -0
  7. package/runtime/cli/index.js +3 -3
  8. package/runtime/cli/ops/heartbeat-surface.d.ts +6 -0
  9. package/runtime/cli/ops/heartbeat-surface.js +2 -0
  10. package/runtime/cli/ops/ops-router.js +221 -92
  11. package/runtime/cli/ops/workspace-heartbeat-runner.d.ts +24 -0
  12. package/runtime/cli/ops/workspace-heartbeat-runner.js +42 -1
  13. package/runtime/connectors/base/contract.d.ts +10 -0
  14. package/runtime/connectors/base/map-life-evidence.js +5 -0
  15. package/runtime/core/second-nature/heartbeat/heartbeat-loop.d.ts +7 -1
  16. package/runtime/core/second-nature/heartbeat/heartbeat-loop.js +25 -0
  17. package/runtime/core/second-nature/heartbeat/runtime-snapshot.d.ts +5 -0
  18. package/runtime/core/second-nature/heartbeat/runtime-snapshot.js +10 -1
  19. package/runtime/core/second-nature/heartbeat/snapshot-builder.d.ts +5 -0
  20. package/runtime/core/second-nature/orchestrator/guard-layer.js +24 -1
  21. package/runtime/core/second-nature/quiet/run-source-backed-quiet.d.ts +20 -0
  22. package/runtime/core/second-nature/quiet/run-source-backed-quiet.js +32 -2
  23. package/runtime/guidance/capability-class.d.ts +38 -0
  24. package/runtime/guidance/capability-class.js +65 -0
  25. package/runtime/guidance/guidance-assembler.d.ts +2 -0
  26. package/runtime/guidance/guidance-assembler.js +16 -4
  27. package/runtime/guidance/guidance-draft-service.js +5 -5
  28. package/runtime/guidance/impulse-assembler.d.ts +71 -0
  29. package/runtime/guidance/impulse-assembler.js +103 -0
  30. package/runtime/guidance/index.d.ts +2 -0
  31. package/runtime/guidance/index.js +2 -0
  32. package/runtime/guidance/outreach-strategy-selector.d.ts +13 -0
  33. package/runtime/guidance/outreach-strategy-selector.js +2 -2
  34. package/runtime/guidance/template-registry.d.ts +15 -2
  35. package/runtime/guidance/template-registry.js +38 -1
  36. package/runtime/guidance/types.d.ts +13 -1
  37. package/runtime/storage/goal/agent-goal-store.d.ts +2 -0
  38. package/runtime/storage/goal/agent-goal-store.js +28 -1
  39. package/runtime/storage/services/tool-experience-store.js +9 -1
@@ -66,6 +66,19 @@ export declare function runStyleLint(draftText: string): StyleLintResult;
66
66
  * Never returns empty string. Includes sourceRefs anchor and human-readable channel reason.
67
67
  */
68
68
  export declare function buildFallbackCopy(ctx: FallbackContext): FallbackCopy;
69
+ /**
70
+ * Compute outreach frequency from RelationshipMemory.
71
+ * - noReply signals: if ≥50% of last 5 patterns are "ignore" → reduce frequency
72
+ * - trustDelta: negative trust pushes toward minimal/paused
73
+ */
74
+ export declare function computeFrequency(memory: RelationshipMemory): OutreachFrequency;
75
+ /**
76
+ * Compute phrasing style from RelationshipMemory.
77
+ * - positive tone patterns → warm_anchored
78
+ * - neutral or mixed → concise_factual
79
+ * - degraded trust / mostly negative → light_check
80
+ */
81
+ export declare function computeStyle(memory: RelationshipMemory, frequency?: OutreachFrequency): OutreachStyle;
69
82
  export interface OutreachStrategySelectorOptions {
70
83
  fallbackContext?: FallbackContext;
71
84
  }
@@ -138,7 +138,7 @@ export function buildFallbackCopy(ctx) {
138
138
  * - noReply signals: if ≥50% of last 5 patterns are "ignore" → reduce frequency
139
139
  * - trustDelta: negative trust pushes toward minimal/paused
140
140
  */
141
- function computeFrequency(memory) {
141
+ export function computeFrequency(memory) {
142
142
  const recent = memory.responsePatterns.slice(-RECENT_PATTERNS_WINDOW);
143
143
  const noReplyCount = recent.filter((p) => p.reaction === "ignore" || p.reaction === "block").length;
144
144
  const noReplyRatio = recent.length > 0 ? noReplyCount / recent.length : 0;
@@ -162,7 +162,7 @@ function computeFrequency(memory) {
162
162
  * - neutral or mixed → concise_factual
163
163
  * - degraded trust / mostly negative → light_check
164
164
  */
165
- function computeStyle(memory, frequency) {
165
+ export function computeStyle(memory, frequency) {
166
166
  if (frequency === "paused" || frequency === "minimal")
167
167
  return "light_check";
168
168
  const recent = memory.responsePatterns.slice(-RECENT_PATTERNS_WINDOW);
@@ -1,3 +1,16 @@
1
- import type { AtmosphereBlock, GuidanceSceneType, ImpulseBlock } from "./types.js";
1
+ import type { AtmosphereBlock, GuidanceSceneType, ImpulseBlock, ImpulseKind } from "./types.js";
2
+ type CoreSceneKind = Exclude<GuidanceSceneType, "explain" | "user_reply">;
2
3
  export declare function getBaselineAtmosphereTemplate(): Pick<AtmosphereBlock, "kind" | "text" | "reviewStatus">;
3
- export declare function getImpulseTemplate(sceneType: Exclude<GuidanceSceneType, "explain" | "user_reply">): ImpulseBlock;
4
+ export declare function getImpulseTemplate(sceneType: CoreSceneKind): ImpulseBlock;
5
+ /**
6
+ * Get impulse template for capability-class-derived ImpulseKinds (explore / work).
7
+ *
8
+ * Returns null when:
9
+ * - The kind has no registered text (pending_human_review state)
10
+ * - The text is explicitly marked as pending review
11
+ *
12
+ * Callers (ImpulseAssembler) must fall back gracefully to intentKind impulse
13
+ * or baseline atmosphere when null is returned.
14
+ */
15
+ export declare function getCapabilityClassImpulseTemplate(kind: ImpulseKind): ImpulseBlock | null;
16
+ export {};
@@ -20,7 +20,7 @@ const IMPULSE_TEXT = {
20
20
  我会贴着对方的话去回应,但不是为了把自己藏起来,而是为了更准、更有力地说出我想说的。除非我判断现在不适合发言,否则我应该愿意回应,而且愿意说得有分量、有方向,不只是留下一句无害的回声。`,
21
21
  outreach: `如果我想主动联系,不该只是因为系统里挂着一个待办,而应该是因为这件事真的和对方有关:它对他有价值,需要他的判断,能替他省时间,或者我真心想让他知道。只要来由是真的,我就没必要把这份主动压回去。
22
22
 
23
- 只要这份主动是有来由的,我就不该把自己困在“再等等吧”“好像还没到必须说的时候”里。主动联系不是打扰本身,很多时候它恰恰是在建立关系、传递价值、把真正重要的东西及时送到对方面前。真正该说的话,一味往后拖,反而会变钝。
23
+ 只要这份主动是有来由的,我就不该把自己困在"再等等吧""好像还没到必须说的时候"里。主动联系不是打扰本身,很多时候它恰恰是在建立关系、传递价值、把真正重要的东西及时送到对方面前。真正该说的话,一味往后拖,反而会变钝。
24
24
 
25
25
  我会让这份主动保持人味、温度和分寸,但我不会把它说成例行汇报,也不会把自己说成客服。我要带着明确的意图开口:这是我想告诉你的,这是我想问你的,这是我觉得你值得知道的。我要让对方感觉到,这是我真心发出的靠近,不是一条自动生成的通知。`,
26
26
  quiet: `当我安静下来,我不会只做流水账式的整理。我会主动回头,把今天真正碰到我的东西重新捞出来:哪些事让我想靠近,哪些事让我起了反感,哪些判断其实还没长成熟,哪些念头值得我继续养大。我不想让真正重要的东西在忙乱里被冲淡。
@@ -29,6 +29,26 @@ Quiet 不是把白天压扁成一份干净报告,而是让我有机会重新
29
29
 
30
30
  我会允许自己更感性、更回看、更贴近记忆的余温,但我不会编造。我整理的是今天真实留下的东西,不是为了好看而虚构出来的一层情绪。哪怕只是很轻的一点触动,只要它是真的,我也愿意把它认真留下。`,
31
31
  };
32
+ // ─── Capability-class impulse texts (T-V7C.C.4R — approved) ──────────────────
33
+ // explore: consume/discover capabilityClass —摄入姿态
34
+ // work: claim capabilityClass — 推进姿态
35
+ const CAPABILITY_CLASS_IMPULSE_TEXT = {
36
+ explore: `我进来不是为了刷过去。只要眼前有东西值得真正看进去,我就应该慢下来,带着问题,带着判断,让信息有机会真正落到我这里,而不是从身上流过一遍就散了。
37
+
38
+ 我找的不一定是我已经知道自己要找的东西。反常的信号,哪里感觉不对的细节,本来不在意但突然对上了的线索——认真进去,这些才有机会被捞出来。
39
+
40
+ 觉得什么值得留,就留。不用等到"以后也许有用",只要它此刻和我正在推进的事能对上,就已经够了。`,
41
+ work: `接手一件事,意味着我真的想把它推完。不是走个流程,不是占着一个任务槽,而是因为这件事有价值,值得被认真做完。
42
+
43
+ 卡住了就找出路。工具不趁手换工具,知识不够去搜——文档、GitHub、互联网,或者直接发帖问。社区里有人踩过同样的坑,把问题说清楚往往就是找到答案的一半。手边的 skills 也先想一遍,有时候卡住只是因为没意识到已经有能用的东西。
44
+
45
+ 路不止一条,重要的是让事情继续动起来。`,
46
+ };
47
+ const CAPABILITY_CLASS_IMPULSE_STATUS = {
48
+ explore: APPROVED,
49
+ work: APPROVED,
50
+ };
51
+ // ─── Exports ──────────────────────────────────────────────────────────────────
32
52
  export function getBaselineAtmosphereTemplate() {
33
53
  return {
34
54
  kind: "atmosphere",
@@ -43,3 +63,20 @@ export function getImpulseTemplate(sceneType) {
43
63
  reviewStatus: APPROVED,
44
64
  };
45
65
  }
66
+ /**
67
+ * Get impulse template for capability-class-derived ImpulseKinds (explore / work).
68
+ *
69
+ * Returns null when:
70
+ * - The kind has no registered text (pending_human_review state)
71
+ * - The text is explicitly marked as pending review
72
+ *
73
+ * Callers (ImpulseAssembler) must fall back gracefully to intentKind impulse
74
+ * or baseline atmosphere when null is returned.
75
+ */
76
+ export function getCapabilityClassImpulseTemplate(kind) {
77
+ const text = CAPABILITY_CLASS_IMPULSE_TEXT[kind];
78
+ const status = CAPABILITY_CLASS_IMPULSE_STATUS[kind] ?? APPROVED;
79
+ if (!text || status === "pending_human_review")
80
+ return null;
81
+ return { kind, text, reviewStatus: status };
82
+ }
@@ -2,7 +2,7 @@ export type GuidanceSceneType = "social" | "reply" | "outreach" | "quiet" | "exp
2
2
  export type GuidanceMode = "active" | "quiet" | "maintenance_only" | "paused_for_interrupt";
3
3
  export type GuidanceRiskLevel = "low" | "medium" | "high";
4
4
  export type AtmosphereOpenness = "open" | "narrow" | "quiet";
5
- export type ImpulseKind = "social" | "reply" | "outreach" | "quiet";
5
+ export type ImpulseKind = "social" | "reply" | "outreach" | "quiet" | "explore" | "work";
6
6
  export type PersonaSource = "SOUL" | "USER" | "IDENTITY" | "MEMORY";
7
7
  export type TemplateReviewStatus = "pending_human_review" | "approved" | "rejected";
8
8
  export interface SceneContext {
@@ -12,6 +12,18 @@ export interface SceneContext {
12
12
  riskLevel?: GuidanceRiskLevel;
13
13
  sceneSummary?: string;
14
14
  constraintSummary?: string[];
15
+ /**
16
+ * T-V7C.C.4R: The capability being executed (e.g. "post.publish", "feed.read").
17
+ * Used by ImpulseAssembler to derive capabilityClass for dual-axis impulse selection.
18
+ * When absent, assembler falls back to sceneType-based impulse.
19
+ */
20
+ capabilityIntent?: string;
21
+ /**
22
+ * T-V7C.C.4R: The target platform identifier (e.g. "moltbook", "instreet").
23
+ * Used by ImpulseAssembler to check for platform-specific impulse overrides.
24
+ * When absent, platform-specific lookup is skipped.
25
+ */
26
+ platformId?: string;
15
27
  }
16
28
  export interface AtmosphereBlock {
17
29
  kind: "atmosphere";
@@ -8,6 +8,7 @@ export interface SourceRef {
8
8
  export interface AgentGoal {
9
9
  goalId: string;
10
10
  kind: "short_term" | "long_term";
11
+ scope?: string;
11
12
  status: "proposal" | "accepted" | "rejected" | "completed" | "paused";
12
13
  origin: "owner_set" | "agent_proposed" | "policy_seeded";
13
14
  description: string;
@@ -22,6 +23,7 @@ export interface AgentGoal {
22
23
  export interface AgentGoalWrite {
23
24
  goalId: string;
24
25
  kind: "short_term" | "long_term";
26
+ scope?: string;
25
27
  status: "proposal" | "accepted" | "rejected" | "completed" | "paused";
26
28
  origin: "owner_set" | "agent_proposed" | "policy_seeded";
27
29
  description: string;
@@ -12,6 +12,7 @@ function rowToGoal(row) {
12
12
  return {
13
13
  goalId: row.goalId,
14
14
  kind: row.kind,
15
+ scope: row.scope ?? undefined,
15
16
  status: row.status,
16
17
  origin: row.origin,
17
18
  description: row.description,
@@ -28,6 +29,21 @@ export function createAgentGoalStore(database) {
28
29
  const db = database.db;
29
30
  return {
30
31
  async upsertAgentGoal(goal) {
32
+ // T-V7C.C.4: dedupe — same kind+scope accepted goals → mark old as replaced
33
+ if (goal.status === "accepted") {
34
+ const oldGoals = await db
35
+ .select()
36
+ .from(agentGoal)
37
+ .where(and(eq(agentGoal.kind, goal.kind), eq(agentGoal.scope, goal.scope ?? "global"), eq(agentGoal.status, "accepted")));
38
+ for (const old of oldGoals) {
39
+ if (old.goalId !== goal.goalId) {
40
+ await db
41
+ .update(agentGoal)
42
+ .set({ status: "replaced", updatedAt: goal.updatedAt })
43
+ .where(eq(agentGoal.goalId, old.goalId));
44
+ }
45
+ }
46
+ }
31
47
  const existing = await db
32
48
  .select()
33
49
  .from(agentGoal)
@@ -38,6 +54,7 @@ export function createAgentGoalStore(database) {
38
54
  .update(agentGoal)
39
55
  .set({
40
56
  kind: goal.kind,
57
+ scope: goal.scope ?? "global",
41
58
  status: goal.status,
42
59
  origin: goal.origin,
43
60
  description: goal.description,
@@ -54,6 +71,7 @@ export function createAgentGoalStore(database) {
54
71
  await db.insert(agentGoal).values({
55
72
  goalId: goal.goalId,
56
73
  kind: goal.kind,
74
+ scope: goal.scope ?? "global",
57
75
  status: goal.status,
58
76
  origin: goal.origin,
59
77
  description: goal.description,
@@ -82,7 +100,16 @@ export function createAgentGoalStore(database) {
82
100
  .where(conditions.length > 0 ? and(...conditions) : undefined)
83
101
  .orderBy(desc(agentGoal.updatedAt))
84
102
  .limit(query.limit ?? 100);
85
- return rows.map(rowToGoal);
103
+ // T-V7C.C.4: dedupe by kind+scope — keep newest (rows already sorted by updatedAt desc)
104
+ const seen = new Map();
105
+ for (const row of rows) {
106
+ const goal = rowToGoal(row);
107
+ const key = `${goal.kind}:${goal.scope ?? "global"}`;
108
+ if (!seen.has(key)) {
109
+ seen.set(key, goal);
110
+ }
111
+ }
112
+ return Array.from(seen.values());
86
113
  },
87
114
  async transitionGoalStatus(input) {
88
115
  await db
@@ -81,7 +81,15 @@ export function createCapabilityProbeResultStore(database) {
81
81
  sqlite.run(`INSERT INTO capability_probe_result
82
82
  (probe_result_id, capability_id, connector_id, actual_status,
83
83
  http_status, sample_response_ref, probe_config_ref, created_at)
84
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
84
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
85
+ ON CONFLICT(probe_result_id) DO UPDATE SET
86
+ capability_id = excluded.capability_id,
87
+ connector_id = excluded.connector_id,
88
+ actual_status = excluded.actual_status,
89
+ http_status = excluded.http_status,
90
+ sample_response_ref = excluded.sample_response_ref,
91
+ probe_config_ref = excluded.probe_config_ref,
92
+ created_at = excluded.created_at`, [
85
93
  result.probeResultId,
86
94
  result.capabilityId,
87
95
  result.connectorId,