@haaaiawd/second-nature 0.1.25 → 0.1.27

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 (37) hide show
  1. package/SKILL.md +33 -0
  2. package/agent-inner-guide.md +124 -0
  3. package/index.js +206 -2
  4. package/openclaw.plugin.json +2 -2
  5. package/package.json +3 -1
  6. package/runtime/cli/commands/goal.d.ts +2 -0
  7. package/runtime/cli/commands/goal.js +5 -1
  8. package/runtime/cli/commands/index.js +1 -1
  9. package/runtime/cli/explain/resolve-subject.js +3 -0
  10. package/runtime/cli/ops/ops-router.js +13 -5
  11. package/runtime/cli/ops/workspace-heartbeat-runner.d.ts +6 -0
  12. package/runtime/cli/ops/workspace-heartbeat-runner.js +35 -1
  13. package/runtime/cli/read-models/index.js +81 -10
  14. package/runtime/cli/read-models/types.d.ts +10 -3
  15. package/runtime/connectors/base/manifest.d.ts +77 -77
  16. package/runtime/core/second-nature/feedback/owner-reply-feedback.d.ts +46 -0
  17. package/runtime/core/second-nature/feedback/owner-reply-feedback.js +159 -0
  18. package/runtime/core/second-nature/heartbeat/heartbeat-loop.d.ts +8 -1
  19. package/runtime/core/second-nature/heartbeat/heartbeat-loop.js +45 -4
  20. package/runtime/core/second-nature/heartbeat/runtime-snapshot.d.ts +2 -0
  21. package/runtime/core/second-nature/heartbeat/runtime-snapshot.js +1 -1
  22. package/runtime/core/second-nature/heartbeat/snapshot-builder.d.ts +16 -2
  23. package/runtime/core/second-nature/index.d.ts +1 -0
  24. package/runtime/core/second-nature/index.js +1 -0
  25. package/runtime/core/second-nature/orchestrator/goal-priority.d.ts +14 -2
  26. package/runtime/core/second-nature/orchestrator/goal-priority.js +2 -2
  27. package/runtime/core/second-nature/orchestrator/intent-planner.d.ts +29 -1
  28. package/runtime/core/second-nature/orchestrator/intent-planner.js +154 -79
  29. package/runtime/core/second-nature/orchestrator/narrative-update.js +23 -9
  30. package/runtime/core/second-nature/orchestrator/platform-capability-router.d.ts +34 -0
  31. package/runtime/core/second-nature/orchestrator/platform-capability-router.js +115 -0
  32. package/runtime/observability/query/explain-query.d.ts +3 -0
  33. package/runtime/observability/query/explain-query.js +9 -0
  34. package/runtime/shared/types/credential.d.ts +1 -1
  35. package/runtime/storage/chronicle/session-chronicle-store.d.ts +1 -1
  36. package/runtime/storage/services/credential-vault.d.ts +18 -0
  37. package/runtime/storage/services/credential-vault.js +96 -12
@@ -1,5 +1,5 @@
1
1
  import type { StateDatabase } from "../db/index.js";
2
- export type ChronicleEventKind = "heartbeat" | "connector_action" | "outreach" | "owner_reply" | "dream_run" | "maintenance";
2
+ export type ChronicleEventKind = "heartbeat" | "connector_action" | "outreach" | "owner_reply" | "dream_run" | "maintenance" | "system_notice";
3
3
  export interface SourceRef {
4
4
  sourceId: string;
5
5
  kind: string;
@@ -10,4 +10,22 @@ export interface CredentialVault {
10
10
  loadCredentialContext(platformId: string): Promise<CredentialContext | null>;
11
11
  getCredentialState(platformId: string): Promise<CredentialState>;
12
12
  }
13
+ /** T1.4.1 — runtime secret health probe result for a single credential row. */
14
+ export interface CredentialHealthProbe {
15
+ platformId: string;
16
+ state: CredentialState | "decrypt_failed";
17
+ keyHealth: "missing_key" | "wrong_key" | "ok";
18
+ hasBaseUrl: boolean;
19
+ diagnosticCode: "missing_runtime_secret" | "credential_recovery_required" | "ok";
20
+ }
21
+ /**
22
+ * T1.4.1 — probe a credential record for runtime secret health.
23
+ *
24
+ * Given a raw encrypted value from the DB, this function checks:
25
+ * 1. Is SECOND_NATURE_ENCRYPTION_KEY present and >= 32 chars?
26
+ * 2. Can the ciphertext be decrypted with that key?
27
+ *
28
+ * It never throws; all failures are encoded in the returned state.
29
+ */
30
+ export declare function probeCredentialHealth(platformId: string, encryptedValue: string | undefined | null, baseUrl: string | undefined | null): CredentialHealthProbe;
13
31
  export declare function createCredentialVault(db: StateDatabase["db"]): CredentialVault;
@@ -8,6 +8,11 @@ import * as crypto from "crypto";
8
8
  import { eq } from "drizzle-orm";
9
9
  import { credentialRecords } from "../db/schema/index.js";
10
10
  const ALGORITHM = "aes-256-gcm";
11
+ function normalizeCredentialRecord(record) {
12
+ return record && typeof record === "object"
13
+ ? record
14
+ : {};
15
+ }
11
16
  function resolveKeyBuffer() {
12
17
  const raw = process.env.SECOND_NATURE_ENCRYPTION_KEY?.trim();
13
18
  if (!raw || raw.length < 32) {
@@ -54,6 +59,58 @@ export function decryptCredentialAtRest(ciphertext) {
54
59
  return "";
55
60
  return decryptInternal(ciphertext);
56
61
  }
62
+ /**
63
+ * T1.4.1 — probe a credential record for runtime secret health.
64
+ *
65
+ * Given a raw encrypted value from the DB, this function checks:
66
+ * 1. Is SECOND_NATURE_ENCRYPTION_KEY present and >= 32 chars?
67
+ * 2. Can the ciphertext be decrypted with that key?
68
+ *
69
+ * It never throws; all failures are encoded in the returned state.
70
+ */
71
+ export function probeCredentialHealth(platformId, encryptedValue, baseUrl) {
72
+ // Key availability check
73
+ const rawKey = process.env.SECOND_NATURE_ENCRYPTION_KEY?.trim();
74
+ if (!rawKey || rawKey.length < 32) {
75
+ return {
76
+ platformId,
77
+ state: encryptedValue ? "decrypt_failed" : "missing",
78
+ keyHealth: "missing_key",
79
+ hasBaseUrl: Boolean(baseUrl),
80
+ diagnosticCode: "missing_runtime_secret",
81
+ };
82
+ }
83
+ // No encrypted value to test
84
+ if (!encryptedValue) {
85
+ return {
86
+ platformId,
87
+ state: "missing",
88
+ keyHealth: "ok",
89
+ hasBaseUrl: Boolean(baseUrl),
90
+ diagnosticCode: "ok",
91
+ };
92
+ }
93
+ // Decryption attempt
94
+ try {
95
+ decryptCredentialAtRest(encryptedValue);
96
+ return {
97
+ platformId,
98
+ state: "active",
99
+ keyHealth: "ok",
100
+ hasBaseUrl: Boolean(baseUrl),
101
+ diagnosticCode: "ok",
102
+ };
103
+ }
104
+ catch {
105
+ return {
106
+ platformId,
107
+ state: "decrypt_failed",
108
+ keyHealth: "wrong_key",
109
+ hasBaseUrl: Boolean(baseUrl),
110
+ diagnosticCode: "credential_recovery_required",
111
+ };
112
+ }
113
+ }
57
114
  export function createCredentialVault(db) {
58
115
  return {
59
116
  async saveCredentialContext(input) {
@@ -88,29 +145,56 @@ export function createCredentialVault(db) {
88
145
  });
89
146
  if (!record)
90
147
  return null;
148
+ const row = normalizeCredentialRecord(record);
149
+ const resolvedPlatformId = row.platformId ?? row.platform_id ?? platformId;
150
+ const credentialType = row.credentialType ?? row.credential_type ?? "api_key";
151
+ const encryptedValue = row.encryptedValue ?? row.encrypted_value ?? "";
152
+ const verificationCode = row.verificationCode ?? row.verification_code ?? undefined;
153
+ const challengeText = row.challengeText ?? row.challenge_text ?? undefined;
154
+ const expiresAt = row.expiresAt ?? row.expires_at ?? undefined;
155
+ const attemptsRemaining = row.attemptsRemaining ?? row.attempts_remaining ?? undefined;
91
156
  let plain;
92
- if (record.encryptedValue) {
93
- if (!isCredentialCiphertext(record.encryptedValue)) {
94
- throw new Error("credential_store_plaintext_or_invalid_legacy_record");
157
+ let status = (row.status ?? "missing");
158
+ if (encryptedValue) {
159
+ if (!isCredentialCiphertext(encryptedValue)) {
160
+ // Fail-closed: return decrypt_failed so callers do not crash.
161
+ return {
162
+ platformId: resolvedPlatformId,
163
+ credentialType: credentialType,
164
+ status: "decrypt_failed",
165
+ encryptedValue: undefined,
166
+ verificationCode,
167
+ challengeText,
168
+ verificationDeadline: expiresAt,
169
+ attemptsRemaining,
170
+ };
171
+ }
172
+ try {
173
+ plain = decryptCredentialAtRest(encryptedValue);
174
+ }
175
+ catch {
176
+ // Decryption failure must not break the whole state load.
177
+ status = "decrypt_failed";
178
+ plain = undefined;
95
179
  }
96
- plain = decryptCredentialAtRest(record.encryptedValue);
97
180
  }
98
181
  return {
99
- platformId: record.platformId,
100
- credentialType: record.credentialType,
101
- status: record.status,
182
+ platformId: resolvedPlatformId,
183
+ credentialType: credentialType,
184
+ status,
102
185
  encryptedValue: plain,
103
- verificationCode: record.verificationCode ?? undefined,
104
- challengeText: record.challengeText ?? undefined,
105
- verificationDeadline: record.expiresAt ?? undefined,
106
- attemptsRemaining: record.attemptsRemaining ?? undefined,
186
+ verificationCode,
187
+ challengeText,
188
+ verificationDeadline: expiresAt,
189
+ attemptsRemaining,
107
190
  };
108
191
  },
109
192
  async getCredentialState(platformId) {
110
193
  const record = await db.query.credentialRecords.findFirst({
111
194
  where: (tbl) => eq(tbl.platformId, platformId),
112
195
  });
113
- return record?.status || "missing";
196
+ const row = normalizeCredentialRecord(record);
197
+ return row.status || "missing";
114
198
  },
115
199
  };
116
200
  }