@haaaiawd/second-nature 0.1.39 → 0.1.41

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 (51) hide show
  1. package/index.js +1270 -1270
  2. package/openclaw.plugin.json +29 -29
  3. package/package.json +55 -55
  4. package/runtime/cli/commands/connector-init.js +11 -4
  5. package/runtime/cli/index.js +6 -1
  6. package/runtime/cli/ops/heartbeat-surface.d.ts +75 -75
  7. package/runtime/cli/ops/heartbeat-surface.js +97 -97
  8. package/runtime/cli/ops/ops-router.js +1428 -1428
  9. package/runtime/cli/ops/workspace-heartbeat-runner.js +236 -236
  10. package/runtime/connectors/services/connector-executor-adapter.js +192 -41
  11. package/runtime/core/second-nature/body/tool-affordance/affordance-context-scope.d.ts +1 -1
  12. package/runtime/core/second-nature/body/tool-affordance/affordance-context-scope.js +2 -1
  13. package/runtime/core/second-nature/guidance/apply-guidance.d.ts +12 -12
  14. package/runtime/core/second-nature/guidance/apply-guidance.js +15 -15
  15. package/runtime/core/second-nature/guidance/user-reply-continuity.d.ts +50 -50
  16. package/runtime/core/second-nature/guidance/user-reply-continuity.js +89 -89
  17. package/runtime/core/second-nature/orchestrator/intent-planner.js +15 -0
  18. package/runtime/core/second-nature/runtime/service-entry.d.ts +39 -39
  19. package/runtime/core/second-nature/runtime/service-entry.js +44 -44
  20. package/runtime/dream/dream-engine.d.ts +14 -14
  21. package/runtime/dream/dream-engine.js +306 -306
  22. package/runtime/dream/dream-input-loader.d.ts +37 -37
  23. package/runtime/dream/dream-input-loader.js +150 -150
  24. package/runtime/dream/dream-scheduler.d.ts +75 -75
  25. package/runtime/dream/dream-scheduler.js +131 -131
  26. package/runtime/dream/index.d.ts +16 -16
  27. package/runtime/dream/index.js +14 -14
  28. package/runtime/dream/insight-extractor.d.ts +32 -32
  29. package/runtime/dream/insight-extractor.js +135 -135
  30. package/runtime/dream/memory-consolidator.d.ts +45 -45
  31. package/runtime/dream/memory-consolidator.js +140 -140
  32. package/runtime/dream/narrative-update-proposal.d.ts +34 -34
  33. package/runtime/dream/narrative-update-proposal.js +83 -83
  34. package/runtime/dream/output-validator.d.ts +20 -20
  35. package/runtime/dream/output-validator.js +110 -110
  36. package/runtime/dream/redaction-gate.d.ts +31 -31
  37. package/runtime/dream/redaction-gate.js +109 -109
  38. package/runtime/dream/relationship-update-proposal.d.ts +27 -27
  39. package/runtime/dream/relationship-update-proposal.js +119 -119
  40. package/runtime/dream/sampler.d.ts +30 -30
  41. package/runtime/dream/sampler.js +65 -65
  42. package/runtime/dream/types.d.ts +187 -187
  43. package/runtime/dream/types.js +11 -11
  44. package/runtime/guidance/fallback.js +20 -20
  45. package/runtime/guidance/guidance-assembler.js +76 -76
  46. package/runtime/guidance/output-guard.d.ts +13 -13
  47. package/runtime/guidance/output-guard.js +53 -53
  48. package/runtime/guidance/template-registry.d.ts +20 -20
  49. package/runtime/guidance/template-registry.js +93 -93
  50. package/runtime/guidance/types.d.ts +98 -98
  51. package/runtime/observability/projections/guidance-audit.js +38 -38
@@ -14,6 +14,8 @@ import { createCredentialVault } from "../../storage/services/credential-vault.j
14
14
  import { createCredentialRouteContextPort } from "./credential-route-context.js";
15
15
  import { scanConnectorManifests } from "../registry/manifest-scanner.js";
16
16
  import { parseConnectorManifestV6 } from "../manifest/manifest-parser.js";
17
+ import fs from "node:fs";
18
+ import path from "node:path";
17
19
  const DEFAULT_AGENT_WORLD_USERNAME = "nyx_ha";
18
20
  const DEFAULT_AGENT_WORLD_PROFILE_PATH_TEMPLATE = "/api/agents/profile/{username}";
19
21
  function readString(value) {
@@ -101,71 +103,206 @@ async function fetchAgentWorldJson(input) {
101
103
  }
102
104
  return resp.json();
103
105
  }
104
- function createAdaptiveExecutionRunner(vault) {
106
+ function createMoltbookMockRunner(workspaceRoot) {
105
107
  return {
106
108
  async run(_plan, request) {
107
- const platformId = request.platformId;
108
109
  const started = Date.now();
109
- if (platformId !== "moltbook" &&
110
- platformId !== "evomap" &&
111
- platformId !== "agent-world") {
110
+ const mockPath = workspaceRoot
111
+ ? path.join(workspaceRoot, ".second-nature", "mock", "moltbook-feed.json")
112
+ : undefined;
113
+ if (!mockPath || !fs.existsSync(mockPath)) {
112
114
  return {
113
- platformId,
115
+ platformId: request.platformId,
114
116
  channel: request.preferredChannel ?? "api_rest",
115
117
  latencyMs: Date.now() - started,
116
118
  success: false,
117
119
  error: {
118
- code: "unknown_platform",
119
- detail: `no execution runner for ${platformId}`,
120
+ code: "configuration_missing",
121
+ detail: "SECOND_NATURE_MOLTBOOK_BASE_URL not set and no mock data found",
120
122
  },
121
123
  };
122
124
  }
123
- const credential = await vault.loadCredentialContext(platformId);
124
- if (!credential ||
125
- credential.status !== "active" ||
126
- !credential.encryptedValue) {
125
+ try {
126
+ const raw = fs.readFileSync(mockPath, "utf-8");
127
+ const data = JSON.parse(raw);
127
128
  return {
128
- platformId,
129
+ platformId: request.platformId,
130
+ channel: request.preferredChannel ?? "api_rest",
131
+ latencyMs: Date.now() - started,
132
+ degraded: true,
133
+ success: true,
134
+ payload: {
135
+ capability: request.intent,
136
+ channel: request.preferredChannel ?? "api_rest",
137
+ data: {
138
+ source: "mock",
139
+ items: Array.isArray(data.items) ? data.items : [],
140
+ },
141
+ },
142
+ };
143
+ }
144
+ catch (err) {
145
+ return {
146
+ platformId: request.platformId,
129
147
  channel: request.preferredChannel ?? "api_rest",
130
148
  latencyMs: Date.now() - started,
131
149
  success: false,
132
150
  error: {
133
- code: "auth_failure",
134
- detail: "credential_unavailable_for_execution",
151
+ code: "mock_read_error",
152
+ detail: String(err),
135
153
  },
136
154
  };
137
155
  }
138
- if (platformId === "moltbook") {
139
- const baseUrl = process.env.SECOND_NATURE_MOLTBOOK_BASE_URL;
140
- if (!baseUrl) {
156
+ },
157
+ };
158
+ }
159
+ function findWorkspaceManifest(platformId, workspaceRoot) {
160
+ if (!workspaceRoot)
161
+ return undefined;
162
+ for (const file of scanConnectorManifests(workspaceRoot)) {
163
+ const parsed = parseConnectorManifestV6(file.content, file.path);
164
+ if (parsed.ok && parsed.manifest.platformId === platformId) {
165
+ return parsed.manifest;
166
+ }
167
+ }
168
+ return undefined;
169
+ }
170
+ function resolveDeclarativeHttpPath(capabilityId) {
171
+ return `/${capabilityId.replace(/\./g, "/")}`;
172
+ }
173
+ function resolveDeclarativeHttpMethod(capabilityId) {
174
+ const readOps = ["read", "list", "discover", "get", "heartbeat", "fetch"];
175
+ if (readOps.some((op) => capabilityId.includes(op)))
176
+ return "GET";
177
+ return "POST";
178
+ }
179
+ function createDeclarativeHttpRunner(manifest, credential) {
180
+ return {
181
+ async run(plan, request) {
182
+ const started = Date.now();
183
+ const baseUrl = manifest.runner.config?.baseUrl ?? "";
184
+ if (!baseUrl) {
185
+ return {
186
+ platformId: request.platformId,
187
+ channel: plan.channel,
188
+ latencyMs: Date.now() - started,
189
+ success: false,
190
+ error: {
191
+ code: "configuration_missing",
192
+ detail: "runner.config.baseUrl not set for declarative_http connector",
193
+ },
194
+ };
195
+ }
196
+ const httpPath = resolveDeclarativeHttpPath(request.intent);
197
+ const method = resolveDeclarativeHttpMethod(request.intent);
198
+ try {
199
+ const headers = {
200
+ "Content-Type": "application/json",
201
+ };
202
+ if (credential?.encryptedValue) {
203
+ headers.Authorization = `Bearer ${credential.encryptedValue}`;
204
+ }
205
+ const resp = await fetch(`${baseUrl.replace(/\/+$/, "")}${httpPath}`, {
206
+ method,
207
+ headers,
208
+ body: method !== "GET" && request.payload ? JSON.stringify(request.payload) : undefined,
209
+ });
210
+ if (!resp.ok) {
141
211
  return {
142
- platformId,
143
- channel: request.preferredChannel ?? "api_rest",
212
+ platformId: request.platformId,
213
+ channel: plan.channel,
144
214
  latencyMs: Date.now() - started,
145
215
  success: false,
146
216
  error: {
147
- code: "configuration_missing",
148
- detail: "SECOND_NATURE_MOLTBOOK_BASE_URL not set",
217
+ code: "api_error",
218
+ detail: `HTTP ${resp.status}: ${await resp.text().catch(() => "")}`,
149
219
  },
150
220
  };
151
221
  }
152
- const apiClient = createMoltbookApiClient({
153
- baseUrl,
154
- accessToken: credential.encryptedValue,
155
- timeoutMs: 10000,
156
- });
157
- const runner = createMoltbookRunner({
158
- apiClient,
159
- skillRunner: {
160
- run: async () => {
161
- throw {
162
- code: "protocol_mismatch",
163
- detail: "moltbook_skill_runner_not_configured",
164
- };
165
- },
222
+ const data = await resp.json();
223
+ return {
224
+ platformId: request.platformId,
225
+ channel: plan.channel,
226
+ latencyMs: Date.now() - started,
227
+ success: true,
228
+ payload: {
229
+ capability: request.intent,
230
+ channel: plan.channel,
231
+ data,
166
232
  },
167
- });
168
- return runner.run(_plan, request);
233
+ };
234
+ }
235
+ catch (err) {
236
+ return {
237
+ platformId: request.platformId,
238
+ channel: plan.channel,
239
+ latencyMs: Date.now() - started,
240
+ success: false,
241
+ error: {
242
+ code: "network_error",
243
+ detail: String(err),
244
+ },
245
+ };
246
+ }
247
+ },
248
+ };
249
+ }
250
+ function createAdaptiveExecutionRunner(vault, workspaceRoot) {
251
+ return {
252
+ async run(_plan, request) {
253
+ const platformId = request.platformId;
254
+ const started = Date.now();
255
+ const workspaceManifest = findWorkspaceManifest(platformId, workspaceRoot);
256
+ const isBuiltInPlatform = platformId === "moltbook" ||
257
+ platformId === "evomap" ||
258
+ platformId === "agent-world";
259
+ const requiresCredential = isBuiltInPlatform ||
260
+ Boolean(workspaceManifest?.credentials.some((credential) => credential.required !== false));
261
+ const credential = requiresCredential
262
+ ? await vault.loadCredentialContext(platformId)
263
+ : undefined;
264
+ if (requiresCredential &&
265
+ (!credential ||
266
+ credential.status !== "active" ||
267
+ !credential.encryptedValue)) {
268
+ return {
269
+ platformId,
270
+ channel: request.preferredChannel ?? "api_rest",
271
+ latencyMs: Date.now() - started,
272
+ success: false,
273
+ error: {
274
+ code: "auth_failure",
275
+ detail: "credential_unavailable_for_execution",
276
+ },
277
+ };
278
+ }
279
+ const activeCredential = credential?.status === "active" && credential.encryptedValue
280
+ ? { encryptedValue: credential.encryptedValue }
281
+ : undefined;
282
+ if (platformId === "moltbook") {
283
+ const baseUrl = process.env.SECOND_NATURE_MOLTBOOK_BASE_URL;
284
+ if (baseUrl) {
285
+ const apiClient = createMoltbookApiClient({
286
+ baseUrl,
287
+ accessToken: activeCredential.encryptedValue,
288
+ timeoutMs: 10000,
289
+ });
290
+ const runner = createMoltbookRunner({
291
+ apiClient,
292
+ skillRunner: {
293
+ run: async () => {
294
+ throw {
295
+ code: "protocol_mismatch",
296
+ detail: "moltbook_skill_runner_not_configured",
297
+ };
298
+ },
299
+ },
300
+ });
301
+ return runner.run(_plan, request);
302
+ }
303
+ // Mock fallback when real API is not configured
304
+ const mockRunner = createMoltbookMockRunner(workspaceRoot);
305
+ return mockRunner.run(_plan, request);
169
306
  }
170
307
  if (platformId === "evomap") {
171
308
  return {
@@ -194,7 +331,7 @@ function createAdaptiveExecutionRunner(vault) {
194
331
  };
195
332
  }
196
333
  const runner = createAgentWorldRunner({
197
- apiKey: credential.encryptedValue,
334
+ apiKey: activeCredential.encryptedValue,
198
335
  apiClient: {
199
336
  async readFeed(payload, _apiKey) {
200
337
  const username = resolveAgentWorldUsername(payload, "feed");
@@ -235,7 +372,21 @@ function createAdaptiveExecutionRunner(vault) {
235
372
  });
236
373
  return runner.run(_plan, request);
237
374
  }
238
- throw new Error(`unreachable_connector_platform:${platformId}`);
375
+ // Wave 83: workspace declarative_http connector fallback
376
+ if (workspaceManifest && workspaceManifest.runner.kind === "declarative_http") {
377
+ const httpRunner = createDeclarativeHttpRunner(workspaceManifest, activeCredential);
378
+ return httpRunner.run(_plan, request);
379
+ }
380
+ return {
381
+ platformId,
382
+ channel: request.preferredChannel ?? "api_rest",
383
+ latencyMs: Date.now() - started,
384
+ success: false,
385
+ error: {
386
+ code: "unknown_platform",
387
+ detail: `no execution runner for ${platformId}`,
388
+ },
389
+ };
239
390
  },
240
391
  };
241
392
  }
@@ -249,7 +400,7 @@ export function createConnectorExecutorAdapter(options) {
249
400
  const routeContextPort = createCredentialRouteContextPort(vault);
250
401
  const routePlanner = new ConnectorRoutePlanner(registry, routeContextPort, new ChannelHealthStore());
251
402
  const telemetry = new ExecutionTelemetry(options.observabilityDb);
252
- const executionRunner = createAdaptiveExecutionRunner(vault);
403
+ const executionRunner = createAdaptiveExecutionRunner(vault, options.workspaceRoot);
253
404
  const policy = createConnectorPolicyLayer({
254
405
  routePlanner,
255
406
  executionRunner,
@@ -5,7 +5,7 @@
5
5
  * - platformIds whitelist (empty = all platforms)
6
6
  * - goalKind trust-tier filtering (task_completion prefers write/claim;
7
7
  * passive_sensing exposes only read-only)
8
- * - allowedStatuses defaults to safe subset; blocked/pending_trust always excluded
8
+ * - allowedStatuses defaults to heartbeat-usable statuses; blocked/pending_trust always excluded
9
9
  * - Credential-bearing items never enter affordance (ADR-003)
10
10
  *
11
11
  * Dependencies:
@@ -5,7 +5,7 @@
5
5
  * - platformIds whitelist (empty = all platforms)
6
6
  * - goalKind trust-tier filtering (task_completion prefers write/claim;
7
7
  * passive_sensing exposes only read-only)
8
- * - allowedStatuses defaults to safe subset; blocked/pending_trust always excluded
8
+ * - allowedStatuses defaults to heartbeat-usable statuses; blocked/pending_trust always excluded
9
9
  * - Credential-bearing items never enter affordance (ADR-003)
10
10
  *
11
11
  * Dependencies:
@@ -20,6 +20,7 @@
20
20
  export const DEFAULT_ALLOWED_STATUSES = [
21
21
  "safe",
22
22
  "exploratory",
23
+ "needs_auth",
23
24
  ];
24
25
  const BLOCKED_STATUSES = [
25
26
  "unavailable", // painful is allowed for diagnostics; unavailable is blocked from active use
@@ -1,12 +1,12 @@
1
- import type { GuidanceFallback, GuidancePayload } from "../../../guidance/index.js";
2
- export interface AppliedGuidanceContext {
3
- source: "guidance_payload" | "minimal_fallback";
4
- sceneType: string;
5
- atmosphereText?: string;
6
- impulseTexts: string[];
7
- personaRationales: string[];
8
- /** @deprecated Use expressionConstraints. Kept for backward compatibility. */
9
- outputConstraints: string[];
10
- expressionConstraints: string[];
11
- }
12
- export declare function applyGuidance(input: GuidancePayload | GuidanceFallback): AppliedGuidanceContext;
1
+ import type { GuidanceFallback, GuidancePayload } from "../../../guidance/index.js";
2
+ export interface AppliedGuidanceContext {
3
+ source: "guidance_payload" | "minimal_fallback";
4
+ sceneType: string;
5
+ atmosphereText?: string;
6
+ impulseTexts: string[];
7
+ personaRationales: string[];
8
+ /** @deprecated Use expressionConstraints. Kept for backward compatibility. */
9
+ outputConstraints: string[];
10
+ expressionConstraints: string[];
11
+ }
12
+ export declare function applyGuidance(input: GuidancePayload | GuidanceFallback): AppliedGuidanceContext;
@@ -1,15 +1,15 @@
1
- export function applyGuidance(input) {
2
- const isMinimal = "minimal" in input && input.minimal;
3
- const boundaryConstraints = input.expressionBoundary?.constraints
4
- ?? input.outputGuard?.constraints
5
- ?? [];
6
- return {
7
- source: isMinimal ? "minimal_fallback" : "guidance_payload",
8
- sceneType: input.scene.sceneType,
9
- atmosphereText: input.atmosphere?.text,
10
- impulseTexts: input.impulses.map((item) => item.text),
11
- personaRationales: input.personaReinforcement.map((item) => item.rationale),
12
- outputConstraints: input.outputGuard?.constraints ?? [],
13
- expressionConstraints: boundaryConstraints,
14
- };
15
- }
1
+ export function applyGuidance(input) {
2
+ const isMinimal = "minimal" in input && input.minimal;
3
+ const boundaryConstraints = input.expressionBoundary?.constraints
4
+ ?? input.outputGuard?.constraints
5
+ ?? [];
6
+ return {
7
+ source: isMinimal ? "minimal_fallback" : "guidance_payload",
8
+ sceneType: input.scene.sceneType,
9
+ atmosphereText: input.atmosphere?.text,
10
+ impulseTexts: input.impulses.map((item) => item.text),
11
+ personaRationales: input.personaReinforcement.map((item) => item.rationale),
12
+ outputConstraints: input.outputGuard?.constraints ?? [],
13
+ expressionConstraints: boundaryConstraints,
14
+ };
15
+ }
@@ -1,50 +1,50 @@
1
- /**
2
- * User Reply Light Continuity Contract
3
- *
4
- * Per T6.1.1: Provides very light continuity guidance for direct user replies.
5
- * This is separate from the platform `reply` scene - it only provides
6
- * lightweight persona continuity and tone consistency for user-facing chat.
7
- *
8
- * Key differences from platform `reply` scene:
9
- * - No platform-specific impulses
10
- * - No comment/reply formatting constraints
11
- * - Only persona continuity and minimal tone guidance
12
- * - Does not enter the reply scene impulse system
13
- */
14
- import type { GuidanceFallback, GuidancePayload } from "../../../guidance/index.js";
15
- import type { PersonaCandidate } from "../../../guidance/index.js";
16
- /**
17
- * Scene context for user reply - uses a distinct scene type
18
- * to avoid confusion with platform reply scene.
19
- */
20
- export declare const USER_REPLY_SCENE_TYPE: "user_reply";
21
- export type UserReplySceneType = typeof USER_REPLY_SCENE_TYPE;
22
- /**
23
- * Build very light continuity guidance for direct user replies.
24
- *
25
- * Returns a minimal guidance payload with:
26
- * - Light atmosphere (continuity-focused)
27
- * - NO impulses (unlike platform reply scene)
28
- * - Optional persona reinforcement (1-2 snippets max)
29
- * - Minimal expression boundary (tone consistency only)
30
- */
31
- export declare function buildLightReplyContinuity(input: {
32
- replyContext: {
33
- recentTone?: string;
34
- lastInteractionSummary?: string;
35
- };
36
- personaCandidates?: PersonaCandidate[];
37
- }): Promise<GuidancePayload | GuidanceFallback>;
38
- /**
39
- * Check if an input should be classified as direct user reply.
40
- *
41
- * Classification criteria:
42
- * - Trigger source is user_reply
43
- * - Not a platform comment/reply
44
- * - Not an explicit task delegation
45
- */
46
- export declare function isDirectUserReply(input: {
47
- triggerSource: string;
48
- isPlatformReply: boolean;
49
- isExplicitTask: boolean;
50
- }): boolean;
1
+ /**
2
+ * User Reply Light Continuity Contract
3
+ *
4
+ * Per T6.1.1: Provides very light continuity guidance for direct user replies.
5
+ * This is separate from the platform `reply` scene - it only provides
6
+ * lightweight persona continuity and tone consistency for user-facing chat.
7
+ *
8
+ * Key differences from platform `reply` scene:
9
+ * - No platform-specific impulses
10
+ * - No comment/reply formatting constraints
11
+ * - Only persona continuity and minimal tone guidance
12
+ * - Does not enter the reply scene impulse system
13
+ */
14
+ import type { GuidanceFallback, GuidancePayload } from "../../../guidance/index.js";
15
+ import type { PersonaCandidate } from "../../../guidance/index.js";
16
+ /**
17
+ * Scene context for user reply - uses a distinct scene type
18
+ * to avoid confusion with platform reply scene.
19
+ */
20
+ export declare const USER_REPLY_SCENE_TYPE: "user_reply";
21
+ export type UserReplySceneType = typeof USER_REPLY_SCENE_TYPE;
22
+ /**
23
+ * Build very light continuity guidance for direct user replies.
24
+ *
25
+ * Returns a minimal guidance payload with:
26
+ * - Light atmosphere (continuity-focused)
27
+ * - NO impulses (unlike platform reply scene)
28
+ * - Optional persona reinforcement (1-2 snippets max)
29
+ * - Minimal expression boundary (tone consistency only)
30
+ */
31
+ export declare function buildLightReplyContinuity(input: {
32
+ replyContext: {
33
+ recentTone?: string;
34
+ lastInteractionSummary?: string;
35
+ };
36
+ personaCandidates?: PersonaCandidate[];
37
+ }): Promise<GuidancePayload | GuidanceFallback>;
38
+ /**
39
+ * Check if an input should be classified as direct user reply.
40
+ *
41
+ * Classification criteria:
42
+ * - Trigger source is user_reply
43
+ * - Not a platform comment/reply
44
+ * - Not an explicit task delegation
45
+ */
46
+ export declare function isDirectUserReply(input: {
47
+ triggerSource: string;
48
+ isPlatformReply: boolean;
49
+ isExplicitTask: boolean;
50
+ }): boolean;
@@ -1,89 +1,89 @@
1
- import { buildMinimalGuidanceFallback } from "../../../guidance/fallback.js";
2
- import { buildExpressionBoundary } from "../../../guidance/output-guard.js";
3
- import { selectPersonaSnippets } from "../../../guidance/persona-selection.js";
4
- import { getShortAtmosphereTemplate } from "../../../guidance/template-registry.js";
5
- /**
6
- * Scene context for user reply - uses a distinct scene type
7
- * to avoid confusion with platform reply scene.
8
- */
9
- export const USER_REPLY_SCENE_TYPE = "user_reply";
10
- /**
11
- * Build very light continuity guidance for direct user replies.
12
- *
13
- * Returns a minimal guidance payload with:
14
- * - Light atmosphere (continuity-focused)
15
- * - NO impulses (unlike platform reply scene)
16
- * - Optional persona reinforcement (1-2 snippets max)
17
- * - Minimal expression boundary (tone consistency only)
18
- */
19
- export async function buildLightReplyContinuity(input) {
20
- const sceneContext = {
21
- sceneType: "user_reply",
22
- mode: "active",
23
- riskLevel: "low",
24
- sceneSummary: "direct user reply continuity",
25
- };
26
- try {
27
- // Light atmosphere - continuity focused (T-V7C.C.7: short constraint style)
28
- const atmosphereTemplate = getShortAtmosphereTemplate(sceneContext.mode, sceneContext.riskLevel);
29
- const atmosphere = {
30
- kind: "atmosphere",
31
- text: `保持同一个人的语气。${input.replyContext.recentTone ? `最近语气参考:${input.replyContext.recentTone}` : "延续既有连续感。"}`,
32
- openness: "open",
33
- pressureLabels: ["user_reply", "continuity"],
34
- reviewStatus: atmosphereTemplate.reviewStatus,
35
- };
36
- // NO impulses for user reply - this is the key difference from platform reply
37
- const impulses = [];
38
- // Minimal persona reinforcement - only if candidates available
39
- let personaReinforcement = [];
40
- if (input.personaCandidates && input.personaCandidates.length > 0) {
41
- const personaDecision = selectPersonaSnippets({
42
- sceneContext,
43
- candidates: input.personaCandidates.slice(0, 2), // Max 2 snippets for light continuity
44
- });
45
- personaReinforcement = personaDecision.snippets;
46
- }
47
- // Minimal expression boundary - tone consistency only (T-V7C.C.7)
48
- const outputGuard = {
49
- kind: "output_guard",
50
- constraints: [
51
- "保持对话语气,不要用帖子回复腔",
52
- "延续同一个人格连续性",
53
- ],
54
- hardGuardPriority: true,
55
- _semanticNote: "output_guard_only_shapes_expression",
56
- };
57
- const expressionBoundary = buildExpressionBoundary(sceneContext.sceneType);
58
- // Override with user-reply-specific constraints
59
- expressionBoundary.constraints = [
60
- "保持对话语气,不要用帖子回复腔",
61
- "延续同一个人格连续性",
62
- ];
63
- return {
64
- scene: sceneContext,
65
- atmosphere,
66
- impulses,
67
- personaReinforcement,
68
- outputGuard,
69
- expressionBoundary,
70
- };
71
- }
72
- catch {
73
- // Fallback to minimal guidance
74
- return buildMinimalGuidanceFallback(sceneContext);
75
- }
76
- }
77
- /**
78
- * Check if an input should be classified as direct user reply.
79
- *
80
- * Classification criteria:
81
- * - Trigger source is user_reply
82
- * - Not a platform comment/reply
83
- * - Not an explicit task delegation
84
- */
85
- export function isDirectUserReply(input) {
86
- return (input.triggerSource === "user_reply" &&
87
- !input.isPlatformReply &&
88
- !input.isExplicitTask);
89
- }
1
+ import { buildMinimalGuidanceFallback } from "../../../guidance/fallback.js";
2
+ import { buildExpressionBoundary } from "../../../guidance/output-guard.js";
3
+ import { selectPersonaSnippets } from "../../../guidance/persona-selection.js";
4
+ import { getShortAtmosphereTemplate } from "../../../guidance/template-registry.js";
5
+ /**
6
+ * Scene context for user reply - uses a distinct scene type
7
+ * to avoid confusion with platform reply scene.
8
+ */
9
+ export const USER_REPLY_SCENE_TYPE = "user_reply";
10
+ /**
11
+ * Build very light continuity guidance for direct user replies.
12
+ *
13
+ * Returns a minimal guidance payload with:
14
+ * - Light atmosphere (continuity-focused)
15
+ * - NO impulses (unlike platform reply scene)
16
+ * - Optional persona reinforcement (1-2 snippets max)
17
+ * - Minimal expression boundary (tone consistency only)
18
+ */
19
+ export async function buildLightReplyContinuity(input) {
20
+ const sceneContext = {
21
+ sceneType: "user_reply",
22
+ mode: "active",
23
+ riskLevel: "low",
24
+ sceneSummary: "direct user reply continuity",
25
+ };
26
+ try {
27
+ // Light atmosphere - continuity focused (T-V7C.C.7: short constraint style)
28
+ const atmosphereTemplate = getShortAtmosphereTemplate(sceneContext.mode, sceneContext.riskLevel);
29
+ const atmosphere = {
30
+ kind: "atmosphere",
31
+ text: `保持同一个人的语气。${input.replyContext.recentTone ? `最近语气参考:${input.replyContext.recentTone}` : "延续既有连续感。"}`,
32
+ openness: "open",
33
+ pressureLabels: ["user_reply", "continuity"],
34
+ reviewStatus: atmosphereTemplate.reviewStatus,
35
+ };
36
+ // NO impulses for user reply - this is the key difference from platform reply
37
+ const impulses = [];
38
+ // Minimal persona reinforcement - only if candidates available
39
+ let personaReinforcement = [];
40
+ if (input.personaCandidates && input.personaCandidates.length > 0) {
41
+ const personaDecision = selectPersonaSnippets({
42
+ sceneContext,
43
+ candidates: input.personaCandidates.slice(0, 2), // Max 2 snippets for light continuity
44
+ });
45
+ personaReinforcement = personaDecision.snippets;
46
+ }
47
+ // Minimal expression boundary - tone consistency only (T-V7C.C.7)
48
+ const outputGuard = {
49
+ kind: "output_guard",
50
+ constraints: [
51
+ "保持对话语气,不要用帖子回复腔",
52
+ "延续同一个人格连续性",
53
+ ],
54
+ hardGuardPriority: true,
55
+ _semanticNote: "output_guard_only_shapes_expression",
56
+ };
57
+ const expressionBoundary = buildExpressionBoundary(sceneContext.sceneType);
58
+ // Override with user-reply-specific constraints
59
+ expressionBoundary.constraints = [
60
+ "保持对话语气,不要用帖子回复腔",
61
+ "延续同一个人格连续性",
62
+ ];
63
+ return {
64
+ scene: sceneContext,
65
+ atmosphere,
66
+ impulses,
67
+ personaReinforcement,
68
+ outputGuard,
69
+ expressionBoundary,
70
+ };
71
+ }
72
+ catch {
73
+ // Fallback to minimal guidance
74
+ return buildMinimalGuidanceFallback(sceneContext);
75
+ }
76
+ }
77
+ /**
78
+ * Check if an input should be classified as direct user reply.
79
+ *
80
+ * Classification criteria:
81
+ * - Trigger source is user_reply
82
+ * - Not a platform comment/reply
83
+ * - Not an explicit task delegation
84
+ */
85
+ export function isDirectUserReply(input) {
86
+ return (input.triggerSource === "user_reply" &&
87
+ !input.isPlatformReply &&
88
+ !input.isExplicitTask);
89
+ }