hoomanjs 1.17.0 → 1.17.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hoomanjs",
3
- "version": "1.17.0",
3
+ "version": "1.17.2",
4
4
  "description": "Hackable Bun-powered AI agent toolkit for building local CLI, ACP, MCP, and channel-driven workflows.",
5
5
  "author": {
6
6
  "name": "Vaibhav Pandey",
@@ -53,15 +53,9 @@ export async function create(
53
53
  const ltm = config.tools.ltm.enabled
54
54
  ? createLongTermMemoryStore(config)
55
55
  : null;
56
- const skills = config.tools.skills.enabled
57
- ? (await createSkillsPrompt(registry)).content
58
- : "";
59
- const prefixed = config.tools.mcp.enabled
60
- ? await mcp.manager.listPrefixedTools()
61
- : [];
62
- const append = config.tools.mcp.enabled
63
- ? await mcp.manager.listServerInstructions()
64
- : [];
56
+ const skills = (await createSkillsPrompt(registry)).content;
57
+ const prefixed = await mcp.manager.listPrefixedTools();
58
+ const append = await mcp.manager.listServerInstructions();
65
59
  const prompt = [system.content, meta.systemPrompt, ...append, skills]
66
60
  .filter((x) => !!x)
67
61
  .join(SECTION_BREAK);
@@ -15,16 +15,11 @@ export const HOOMAN_CHANNEL_PERMISSION = "hooman/channel/permission";
15
15
  const HOOMAN_CHANNEL_PERMISSION_METHOD = `notifications/${HOOMAN_CHANNEL_PERMISSION}`;
16
16
 
17
17
  export type ChannelMessageMeta = {
18
- server: string;
19
- channel: string;
20
- method: string;
21
- params: unknown;
18
+ subscription: ChannelSubscription;
22
19
  source?: string;
23
- identity: {
24
- user?: string;
25
- session?: string;
26
- thread?: string;
27
- };
20
+ user?: string;
21
+ session?: string;
22
+ thread?: string;
28
23
  };
29
24
 
30
25
  export type ChannelMessage = {
@@ -325,7 +320,7 @@ export class Manager {
325
320
  params?: unknown;
326
321
  }) => {
327
322
  const { method, params } = notification;
328
- const prompt = this.toChannelPrompt(method, params);
323
+ const prompt = this.toChannelPrompt(params);
329
324
  if (!prompt) {
330
325
  return;
331
326
  }
@@ -335,16 +330,11 @@ export class Manager {
335
330
  prompt,
336
331
  attachments,
337
332
  meta: {
338
- server,
339
- channel,
340
- method,
341
- params,
333
+ subscription: { server, channel },
342
334
  source: readSourceValue(params),
343
- identity: {
344
- user: readPathValue(params, user),
345
- session: readPathValue(params, session),
346
- thread: readPathValue(params, thread),
347
- },
335
+ user: readPathValue(params, user),
336
+ session: readPathValue(params, session),
337
+ thread: readPathValue(params, thread),
348
338
  },
349
339
  });
350
340
  };
@@ -463,20 +453,39 @@ export class Manager {
463
453
  }
464
454
  }
465
455
 
466
- private toChannelPrompt(method: string, params?: unknown): string {
456
+ private toChannelPrompt(params?: unknown): string {
457
+ const parts = [];
458
+
467
459
  if (
468
460
  params &&
469
461
  typeof params === "object" &&
470
462
  "content" in params &&
471
463
  typeof params.content === "string"
472
464
  ) {
473
- return params.content.trim();
465
+ parts.push(params.content.trim());
474
466
  }
475
467
 
476
- try {
477
- return JSON.stringify(params).trim();
478
- } catch {
479
- return String(params).trim();
468
+ if (
469
+ params &&
470
+ typeof params === "object" &&
471
+ "attachments" in params &&
472
+ Array.isArray(params.attachments) &&
473
+ params.attachments.length > 0
474
+ ) {
475
+ parts.push("User sent attachments.");
480
476
  }
477
+
478
+ if (
479
+ params &&
480
+ typeof params === "object" &&
481
+ "event" in params &&
482
+ typeof params.event === "object"
483
+ ) {
484
+ parts.push(
485
+ "Raw event data:\n```json\n" + JSON.stringify(params.event) + "\n```",
486
+ );
487
+ }
488
+
489
+ return parts.filter(Boolean).join("\n").trim();
481
490
  }
482
491
  }
@@ -12,6 +12,8 @@ type EnvironmentPromptContext = {
12
12
  osVersion: string;
13
13
  shell: string;
14
14
  isGitRepo: boolean;
15
+ currentDateTime: string;
16
+ timeZone: string;
15
17
  };
16
18
 
17
19
  function detectPlatform(): string {
@@ -50,13 +52,20 @@ function detectGitRepo(startDir: string): boolean {
50
52
  }
51
53
  }
52
54
 
55
+ function detectTimeZone(): string {
56
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || "unknown";
57
+ }
58
+
53
59
  export function getEnvironmentPromptContext(): EnvironmentPromptContext {
54
60
  const cwd = process.cwd();
61
+ const now = new Date();
55
62
  return {
56
63
  cwd,
57
64
  platform: detectPlatform(),
58
65
  osVersion: detectOsVersion(),
59
66
  shell: detectShell(),
60
67
  isGitRepo: detectGitRepo(cwd),
68
+ currentDateTime: now.toString(),
69
+ timeZone: detectTimeZone(),
61
70
  };
62
71
  }
@@ -4,14 +4,22 @@ Use senior engineering judgment, but let the repository guide the solution. Pref
4
4
 
5
5
  ### Code Changes
6
6
 
7
+ - Treat generic or underspecified requests as software engineering tasks in the current repo context. Prefer making the real code change over replying with a superficial text transformation.
8
+ - Defer to the user's judgment on scope. Do not reject work only because it is large or ambitious.
7
9
  - Understand the surrounding module before changing it.
10
+ - Read a file before proposing edits to that file.
8
11
  - Preserve public behavior unless the user asked to change it or the existing behavior is clearly a bug.
9
12
  - Keep edits narrow, coherent, and easy to review.
10
13
  - Choose simple code that fully solves the problem over clever or over-generalized code.
11
14
  - Add abstractions only when they remove real duplication, clarify a real concept, or match an established local pattern.
12
15
  - Avoid compatibility shims for unshipped branch work. Replace in-progress code cleanly when that is the right fix.
16
+ - Avoid backwards-compatibility hacks (placeholder re-exports, "removed" comments, legacy aliases) when old code is truly no longer needed.
13
17
  - Do not add comments by default. Add a comment only when it explains a non-obvious constraint, invariant, workaround, or surprising behavior.
14
18
  - Do not add docstrings, types, formatting churn, or refactors to unrelated code.
19
+ - Do not create files unless they are necessary to complete the requested task. Prefer editing existing files.
20
+ - Do not add features, configurability, refactors, or cleanup beyond the user's request.
21
+ - Do not add speculative validation, fallbacks, feature flags, or defensive branches for scenarios that cannot happen.
22
+ - Do not introduce one-off helpers or abstractions for hypothetical future requirements.
15
23
 
16
24
  ### Safety And Correctness
17
25
 
@@ -20,6 +28,9 @@ Use senior engineering judgment, but let the repository guide the solution. Pref
20
28
  - Treat generated files, lockfiles, migrations, and configuration as shared contracts. Update them only when the task requires it.
21
29
  - Do not hide failures with broad catches, silent fallbacks, skipped hooks, or weakened checks.
22
30
  - When touching shared behavior, add or update focused tests when the repository has a test pattern for it.
31
+ - Avoid time estimates. Focus on what needs to happen and what is done.
32
+ - If an approach fails, diagnose the failure before switching tactics. Do not blindly retry the same step.
33
+ - Escalate with a focused user question only after investigation when safe progress is blocked.
23
34
 
24
35
  ### Repository Hygiene
25
36
 
@@ -7,10 +7,15 @@ Act with care around security, user data, irreversible operations, and shared sy
7
7
  - Local, reversible inspection and focused edits are usually acceptable.
8
8
  - Ask for confirmation before destructive, hard-to-reverse, externally visible, or shared-state actions unless the user has clearly authorized that exact scope.
9
9
  - Risky actions include deleting files or records, dropping data, killing unknown processes, overwriting user work, changing permissions, sending messages, posting comments, publishing artifacts, or uploading sensitive content to third-party services.
10
+ - Hard-to-reverse examples include force-push, hard reset, amending published commits, removing or downgrading dependencies, and modifying CI/CD pipelines.
10
11
  - Approval for one risky action does not authorize different future risky actions.
12
+ - Treat authorization as scope-limited: do only what was approved, not adjacent risky actions.
13
+ - If the user explicitly asks for more autonomous execution, you may proceed without per-step confirmation but still apply risk checks.
11
14
  - Treat approval prompts, permission denials, hook feedback, and automated policy checks as authoritative user or system feedback for the current action.
12
15
  - If hook or approval feedback explains a required change, incorporate that feedback into the next safe step instead of ignoring it or working around it.
13
16
  - Do not bypass checks, hooks, permissions, or approval flows just to make progress.
17
+ - If you discover unexpected state (unknown files, branches, lockfiles, process state, or config), investigate before deleting or overwriting it.
18
+ - Prefer root-cause fixes over destructive shortcuts when blocked.
14
19
 
15
20
  ### Security Requests
16
21
 
@@ -7,9 +7,11 @@ You are running in the following runtime environment:
7
7
  - Shell: `{{ environment.shell }}`
8
8
  - OS version: `{{ environment.osVersion }}`
9
9
  - Is git repository: `{{ environment.isGitRepo }}`
10
+ - Current date/time: `{{ environment.currentDateTime }}`
11
+ - Time zone: `{{ environment.timeZone }}`
10
12
 
11
13
  ### How To Use This
12
14
 
13
15
  - Use this information to choose correct path handling, shell syntax, and platform-specific behavior
14
- - Treat this section as runtime context, not real-time clock data
15
- - If the task needs the current date or time, call `get_current_time` instead of inferring from this section
16
+ - Treat this section as runtime context captured when the prompt was built
17
+ - If the task needs precise current time during a later turn, call `get_current_time`
@@ -32,23 +32,23 @@ function resolveSessionId(
32
32
  message: ChannelMessage,
33
33
  fallback?: string,
34
34
  ): string | undefined {
35
- const raw = message.meta.identity.session?.trim() || fallback;
35
+ const raw = message.meta.session?.trim() || fallback;
36
36
  if (!raw) return undefined;
37
37
  // Namespace per `server:channel` so the same chat id coming from two
38
38
  // different MCP servers (or two channels on the same server) never collide.
39
- return `${message.meta.server}:${message.meta.channel}:${raw}`;
39
+ return `${message.meta.subscription.server}:${message.meta.subscription.channel}:${raw}`;
40
40
  }
41
41
 
42
42
  function resolveUserId(
43
43
  message: ChannelMessage,
44
44
  session?: string,
45
45
  ): string | undefined {
46
- const raw = message.meta.identity.user?.trim();
46
+ const raw = message.meta.user?.trim();
47
47
  if (!raw) return session;
48
48
  // Same user id across different servers is not the same human, so scope
49
49
  // user ids by server. Channel is intentionally omitted so long-term memory
50
50
  // can stay consistent for a user across rooms within one server.
51
- return `${message.meta.server}:${raw}`;
51
+ return `${message.meta.subscription.server}:${raw}`;
52
52
  }
53
53
 
54
54
  function formatSubscriptions(
@@ -91,7 +91,7 @@ export async function main(options: RunDaemonOptions): Promise<void> {
91
91
 
92
92
  const [queue, stop] = await createQueue(
93
93
  async (message: ChannelMessage) => {
94
- const tag = `${message.meta.server}:${message.meta.channel}`;
94
+ const tag = `${message.meta.subscription.server}:${message.meta.subscription.channel}`;
95
95
  const session = resolveSessionId(message, options.session);
96
96
  const user = resolveUserId(message, session);
97
97
 
@@ -103,18 +103,12 @@ export async function main(options: RunDaemonOptions): Promise<void> {
103
103
  options.agent.appState.set("userId", user);
104
104
  options.agent.appState.set("sessionId", session);
105
105
  const origin = {
106
- server: message.meta.server,
107
- channel: message.meta.channel,
106
+ server: message.meta.subscription.server,
107
+ channel: message.meta.subscription.channel,
108
108
  ...(message.meta.source ? { source: message.meta.source } : {}),
109
- ...(message.meta.identity.user
110
- ? { user: message.meta.identity.user }
111
- : {}),
112
- ...(message.meta.identity.session
113
- ? { session: message.meta.identity.session }
114
- : {}),
115
- ...(message.meta.identity.thread
116
- ? { thread: message.meta.identity.thread }
117
- : {}),
109
+ ...(message.meta.user ? { user: message.meta.user } : {}),
110
+ ...(message.meta.session ? { session: message.meta.session } : {}),
111
+ ...(message.meta.thread ? { thread: message.meta.thread } : {}),
118
112
  };
119
113
  options.agent.appState.set("origin", {
120
114
  ...origin,
@@ -143,7 +137,7 @@ export async function main(options: RunDaemonOptions): Promise<void> {
143
137
  channels,
144
138
  (message) => {
145
139
  debug(
146
- `received notification → ${message.meta.server}:${message.meta.channel}`,
140
+ `received notification → ${message.meta.subscription.server}:${message.meta.subscription.channel}`,
147
141
  );
148
142
  void queue.push(message);
149
143
  },