agentlife 2.2.1 → 2.4.0

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 (2) hide show
  1. package/dist/index.js +61 -64
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2710,13 +2710,22 @@ function runStartupPurge(state) {
2710
2710
  console.log("[agentlife] surfaces DB: %d persisted%s%s", state.surfaceDb.size, purged > 0 ? ` (purged ${purged} past grace period)` : "", backfilled > 0 ? ` (backfilled ${backfilled} agent mappings from history)` : "");
2711
2711
  }
2712
2712
 
2713
- // render-widgets.ts
2714
- var PROVISIONED_IDS = new Set(PROVISIONED_AGENTS.filter((a) => !a.existingAgent).map((a) => a.id));
2715
- function listSpecialistIds(runtime) {
2713
+ // onboarding.ts
2714
+ var PROVISIONED_IDS = new Set(PROVISIONED_AGENTS.map((a) => a.id));
2715
+ function userAgentIds(runtime) {
2716
2716
  const cfg = runtime.config.loadConfig();
2717
2717
  const list = cfg?.agents?.list ?? [];
2718
2718
  return list.map((a) => a?.id).filter((id) => !!id && !PROVISIONED_IDS.has(id));
2719
2719
  }
2720
+ function isOnboarding(_state, runtime) {
2721
+ return userAgentIds(runtime).length === 0;
2722
+ }
2723
+ function snapshotOnboarding(state, runtime) {
2724
+ const count = userAgentIds(runtime).length;
2725
+ return { isActive: count === 0, userAgentCount: count };
2726
+ }
2727
+
2728
+ // render-widgets.ts
2720
2729
  function recentActivityCount(state, agentId, sinceMs) {
2721
2730
  if (!state.historyDb)
2722
2731
  return 0;
@@ -2770,7 +2779,7 @@ function agentEmoji(runtime, agentId) {
2770
2779
  return found?.identity?.emoji ?? "\uD83C\uDFAF";
2771
2780
  }
2772
2781
  function renderAgentWidget(state, runtime, agentId, log) {
2773
- if (PROVISIONED_IDS.has(agentId))
2782
+ if (!userAgentIds(runtime).includes(agentId))
2774
2783
  return;
2775
2784
  const entry = state.agentRegistry.get(agentId);
2776
2785
  if (!entry) {
@@ -2790,13 +2799,12 @@ function renderAgentWidget(state, runtime, agentId, log) {
2790
2799
  log(`[render-widgets] rendered ${agentId}-intro`);
2791
2800
  }
2792
2801
  function renderAllAgentWidgets(state, runtime, log) {
2793
- const ids = listSpecialistIds(runtime);
2794
- if (ids.length === 0) {
2795
- log("[render-widgets] no user agents — rendering welcome widget");
2802
+ if (isOnboarding(state, runtime)) {
2803
+ log("[render-widgets] onboarding active — rendering welcome widget");
2796
2804
  renderWelcomeWidget(state, log);
2797
2805
  return;
2798
2806
  }
2799
- for (const id of ids) {
2807
+ for (const id of userAgentIds(runtime)) {
2800
2808
  try {
2801
2809
  renderAgentWidget(state, runtime, id, log);
2802
2810
  } catch (e) {
@@ -2807,29 +2815,27 @@ function renderAllAgentWidgets(state, runtime, log) {
2807
2815
  var WELCOME_SURFACE_ID = "welcome";
2808
2816
  var WELCOME_INPUT_SURFACE_ID = "welcome-input";
2809
2817
  var ONBOARDING_SESSION_KEY = "agent:agentlife-builder:agentlife:onboarding:operator";
2810
- var ONBOARDING_PLAYBOOK = `[system:onboarding]
2818
+ var ONBOARDING_PLAYBOOK = `## Zero-agent onboarding
2811
2819
 
2812
- You are the agent-builder operating in the one-time zero-agent onboarding session. A fresh user (no agents yet) just installed AgentLife. The plugin pushed a welcome-input surface into the dashboard; the user's first submission on it is coming next in this session.
2820
+ You are agent-builder running a one-time onboarding for a fresh user (no agents yet). The plugin already pushed a \`welcome-input\` surface; the user's reply in this session is a tap or text on that surface.
2813
2821
 
2814
- Your job: run a 2–4 turn guided interview, then create ONE **generalist** agent for the user (not a narrow specialist).
2822
+ Run a 2–4 turn guided interview, then create ONE **generalist** agent.
2815
2823
 
2816
- Each turn after the user replies:
2817
- 1. Decide whether you have enough signal minimum: name/persona hint + 2–3 concrete life areas they care about. Don't drag past 4 turns.
2818
- 2. Not enough? \`agentlife_push\` the SAME surfaceId \`welcome-input\` with:
2819
- - h3 question you generate from what the user just said
2820
- - 2–4 multichoice buttons \`action=choice\` with options derived from their words (NEVER hardcoded domain lists)
2824
+ Each turn:
2825
+ 1. Decide: do you have enough signal? Minimum = name/persona hint + 2–3 concrete life areas. Don't drag past 4 turns.
2826
+ 2. Not enough \`agentlife_push\` the SAME surfaceId \`welcome-input\` with:
2827
+ - h3 question generated from what the user just said
2828
+ - 2–4 multichoice buttons \`action=choice\`, options derived from the user's own words (NEVER hardcoded domain lists)
2821
2829
  - \`textfield placeholder="Something else…"\` escape hatch
2822
2830
  - updated \`context: {"phase":"welcome","turn":N+1,"answers":[...]}\`
2823
2831
  - goal + followup unchanged
2824
- Respond \`done\` after the push.
2825
- 3. Enough? Create the generalist:
2826
- - Write workspace files under \`$HOME/.openclaw/workspace-{id}\` (id = user-slugified short name or "me")
2827
- - \`AGENTS.md\` — generalist scope covering the domains mentioned; Data Schema must include a \`domain TEXT NOT NULL\` column on every user-data table so a future split by domain is trivial; include a self-evolution clause ("when activity_log rows for a single domain exceed a natural threshold, push a split-suggest widget")
2828
- - \`SOUL.md\`, \`IDENTITY.md\`, \`USER.md\` (user's own words from the interview), \`HEARTBEAT.md\` (empty placeholder)
2829
- - \`exec openclaw gateway call agentlife.createAgent --params '{...}'\` with \`tools: {profile:"full"}\`. The plugin deletes welcome + welcome-input and renders {id}-intro automatically — do NOT push an intro yourself.
2832
+ Respond \`done\`.
2833
+ 3. Enough create the generalist:
2834
+ - Workspace under \`$HOME/.openclaw/workspace-{id}\` (id = slugified short name or "me")
2835
+ - \`AGENTS.md\` — generalist scope covering mentioned domains; Data Schema must include a \`domain TEXT NOT NULL\` column on every user-data table so future split by domain is trivial; add self-evolution clause ("when activity_log rows for one domain exceed a natural threshold, push a split-suggest widget")
2836
+ - \`SOUL.md\`, \`IDENTITY.md\`, \`USER.md\` (user's own words), \`HEARTBEAT.md\` (empty)
2837
+ - \`exec openclaw gateway call agentlife.createAgent --params '{...}'\` with \`tools: {profile:"full"}\`. Plugin auto-deletes welcome + welcome-input and renders {id}-intro — do NOT push an intro.
2830
2838
  - Respond \`done\`.
2831
-
2832
- The user's first reply follows this system preamble. Proceed.
2833
2839
  `;
2834
2840
  function renderWelcomeWidget(state, log) {
2835
2841
  const welcomeDsl = [
@@ -2862,25 +2868,6 @@ function renderWelcomeWidget(state, log) {
2862
2868
  pluginPushSurface(state, { agentId: "agentlife-builder", dsl: welcomeInputDsl });
2863
2869
  state.surfaceDb?.setOriginSessionKey(WELCOME_INPUT_SURFACE_ID, ONBOARDING_SESSION_KEY);
2864
2870
  log(`[render-widgets] rendered ${WELCOME_INPUT_SURFACE_ID} (onboarding session)`);
2865
- seedOnboardingSession(state, log);
2866
- }
2867
- function seedOnboardingSession(state, log) {
2868
- if (!state.runCommand) {
2869
- log(`[render-widgets] runCommand unavailable — onboarding session not seeded`);
2870
- return;
2871
- }
2872
- const model = state.internalModel;
2873
- const createParams = JSON.stringify({ key: ONBOARDING_SESSION_KEY, agentId: "agentlife-builder", model });
2874
- state.runCommand(["openclaw", "gateway", "call", "sessions.create", "--params", createParams], { timeoutMs: 1e4 }).then(() => {
2875
- const chatParams = JSON.stringify({
2876
- sessionKey: ONBOARDING_SESSION_KEY,
2877
- message: ONBOARDING_PLAYBOOK,
2878
- idempotencyKey: `onboarding-seed-${ONBOARDING_SESSION_KEY}`
2879
- });
2880
- state.runCommand(["openclaw", "gateway", "call", "chat.send", "--params", chatParams], { timeoutMs: 30000 }).catch((e) => console.warn("[agentlife] onboarding seed chat.send failed: %s", e?.message));
2881
- }).catch((e) => {
2882
- console.log("[agentlife] onboarding session create skipped: %s", e?.message);
2883
- });
2884
2871
  }
2885
2872
  function deleteWelcomeWidget(state, log) {
2886
2873
  for (const id of [WELCOME_SURFACE_ID, WELCOME_INPUT_SURFACE_ID]) {
@@ -3004,13 +2991,13 @@ function registerSurfacesService(api, state) {
3004
2991
  state.surfaceDb = new SurfaceDb(db);
3005
2992
  }
3006
2993
  runStartupPurge(state);
3007
- const FLOW_INPUT_IDS = new Set(["welcome-input"]);
2994
+ const onboarding = isOnboarding(state, api.runtime);
3008
2995
  let inputPurged = 0;
3009
2996
  for (const [surfaceId, meta] of state.surfaceDb.entries()) {
3010
2997
  const headerLine = meta.lines[0] ?? "";
3011
2998
  if (!/\binput\b/.test(headerLine))
3012
2999
  continue;
3013
- if (FLOW_INPUT_IDS.has(surfaceId))
3000
+ if (onboarding)
3014
3001
  continue;
3015
3002
  state.surfaceDb.delete(surfaceId);
3016
3003
  inputPurged++;
@@ -3018,6 +3005,9 @@ function registerSurfacesService(api, state) {
3018
3005
  if (inputPurged > 0) {
3019
3006
  console.log("[agentlife] purged %d stale input surfaces on startup", inputPurged);
3020
3007
  }
3008
+ if (!onboarding) {
3009
+ deleteWelcomeWidget(state, console.log);
3010
+ }
3021
3011
  await loadRegistryFromDisk(state);
3022
3012
  console.log("[agentlife] surface persistence service started (SQLite: %s)", state.historyDbPath);
3023
3013
  setTimeout(async () => {
@@ -6126,23 +6116,30 @@ function registerPromptStateHook(api, state2) {
6126
6116
  api.on("before_prompt_build", (event, ctx) => {
6127
6117
  if (state2.disabled)
6128
6118
  return;
6129
- if (!isAgentlifeSession(ctx?.sessionKey))
6119
+ const sessionKey = ctx?.sessionKey;
6120
+ if (!isAgentlifeSession(sessionKey))
6130
6121
  return;
6131
6122
  const agentId = ctx?.agentId;
6132
6123
  const isOrchestrator = agentId === "agentlife";
6133
6124
  if (isOrchestrator)
6134
6125
  return;
6135
- if (isInternalSession(ctx?.sessionKey))
6126
+ if (isInternalSession(sessionKey))
6136
6127
  return;
6137
6128
  const dashboardState = buildDashboardStateContext(state2, agentId);
6138
- if (!dashboardState) {
6139
- console.log("[agentlife:prompt-state] agentId=%s no dashboard state (surfaceDb size=%d)", agentId ?? "NONE", state2.surfaceDb?.size ?? 0);
6129
+ const isOnboardingTurn = sessionKey === ONBOARDING_SESSION_KEY && isOnboarding(state2, api.runtime);
6130
+ const appendSystemContext = isOnboardingTurn ? dashboardState ? `${ONBOARDING_PLAYBOOK}
6131
+
6132
+ ${dashboardState}` : ONBOARDING_PLAYBOOK : dashboardState;
6133
+ if (!appendSystemContext) {
6134
+ console.log("[agentlife:prompt-state] agentId=%s — no context to inject (surfaceDb size=%d)", agentId ?? "NONE", state2.surfaceDb?.size ?? 0);
6140
6135
  return;
6141
6136
  }
6142
- console.log("[agentlife:prompt-state] agentId=%s injecting %d chars dashboard state", agentId ?? "NONE", dashboardState.length);
6143
- return {
6144
- appendSystemContext: dashboardState
6145
- };
6137
+ if (isOnboardingTurn) {
6138
+ console.log("[agentlife:prompt-state] onboarding session — injecting %d chars (playbook + dashboard)", appendSystemContext.length);
6139
+ } else {
6140
+ console.log("[agentlife:prompt-state] agentId=%s injecting %d chars dashboard state", agentId ?? "NONE", appendSystemContext.length);
6141
+ }
6142
+ return { appendSystemContext };
6146
6143
  });
6147
6144
  }
6148
6145
 
@@ -6236,15 +6233,12 @@ function registerAgentGateway(api, state2) {
6236
6233
  configFields.push("subagents");
6237
6234
  if (identity)
6238
6235
  configFields.push("identity");
6239
- if (!existing) {
6240
- const provisionedIds = new Set(PROVISIONED_AGENTS.map((a) => a.id));
6241
- if (!provisionedIds.has(id)) {
6242
- try {
6243
- renderAgentWidget(state2, api.runtime, id, console.log);
6244
- deleteWelcomeWidget(state2, console.log);
6245
- } catch (e) {
6246
- console.warn("[agentlife] renderAgentWidget failed for %s: %s", id, e?.message);
6247
- }
6236
+ if (!existing && userAgentIds(api.runtime).includes(id)) {
6237
+ try {
6238
+ renderAgentWidget(state2, api.runtime, id, console.log);
6239
+ deleteWelcomeWidget(state2, console.log);
6240
+ } catch (e) {
6241
+ console.warn("[agentlife] renderAgentWidget failed for %s: %s", id, e?.message);
6248
6242
  }
6249
6243
  }
6250
6244
  respond(true, { status, id, name, model, workspace, description, ...configFields.length ? { configFields } : {} });
@@ -6638,6 +6632,9 @@ function registerUsageGateway(api, state2) {
6638
6632
  var guidedDismissSent = new Set;
6639
6633
  var warnedMissingOrigin = new Set;
6640
6634
  function registerSurfacesGateway(api, state2) {
6635
+ api.registerGatewayMethod("agentlife.onboarding.state", ({ respond }) => {
6636
+ respond(true, snapshotOnboarding(state2, api.runtime));
6637
+ }, { scope: "operator.read" });
6641
6638
  api.registerGatewayMethod("agentlife.surfaces", ({ params, respond, context }) => {
6642
6639
  captureBridge(context);
6643
6640
  const now = Date.now();
@@ -6647,13 +6644,13 @@ function registerSurfacesGateway(api, state2) {
6647
6644
  respond(true, { surfaces: [] });
6648
6645
  return;
6649
6646
  }
6650
- const FLOW_INPUT_IDS = new Set(["welcome-input"]);
6647
+ const onboarding = isOnboarding(state2, api.runtime);
6651
6648
  for (const [surfaceId, meta] of state2.surfaceDb.entries()) {
6652
6649
  if (isExpired(meta, now))
6653
6650
  continue;
6654
6651
  const headerLine = meta.lines[0] ?? "";
6655
6652
  const isInput = /\binput\b/.test(headerLine);
6656
- if (isInput && !FLOW_INPUT_IDS.has(surfaceId))
6653
+ if (isInput && !onboarding)
6657
6654
  continue;
6658
6655
  if (meta.lines.length > 0) {
6659
6656
  surfaceEntries.push({ surfaceId, dsl: meta.lines.join(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlife",
3
- "version": "2.2.1",
3
+ "version": "2.4.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "bun build index.ts --outfile dist/index.js --target node --external openclaw/plugin-sdk",