stella-timeline-plugin 2.0.6 → 2.0.7

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/README.md CHANGED
@@ -63,7 +63,7 @@ It is built for immersive social chat first.
63
63
  ### 1. Install the plugin
64
64
 
65
65
  ```bash
66
- openclaw plugins install stella-timeline-plugin --pin
66
+ openclaw plugins install stella-timeline-plugin
67
67
  openclaw plugins enable stella-timeline-plugin
68
68
  ```
69
69
 
package/README_ZH.md CHANGED
@@ -63,7 +63,7 @@ Timeline 为 OpenClaw 增加了一层专门的时间感知与回忆能力,让
63
63
  ### 1. 安装插件
64
64
 
65
65
  ```bash
66
- openclaw plugins install stella-timeline-plugin --pin
66
+ openclaw plugins install stella-timeline-plugin
67
67
  openclaw plugins enable stella-timeline-plugin
68
68
  ```
69
69
 
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TIMELINE_SKILL_PATHS = exports.TIMELINE_TOOL_NAMES = exports.TIMELINE_PLUGIN_DESCRIPTION = exports.TIMELINE_PLUGIN_VERSION = exports.TIMELINE_PLUGIN_NAME = exports.TIMELINE_PLUGIN_ID = void 0;
4
4
  exports.TIMELINE_PLUGIN_ID = 'stella-timeline-plugin';
5
5
  exports.TIMELINE_PLUGIN_NAME = 'Stella Timeline Plugin';
6
- exports.TIMELINE_PLUGIN_VERSION = '2.0.6';
6
+ exports.TIMELINE_PLUGIN_VERSION = '2.0.7';
7
7
  exports.TIMELINE_PLUGIN_DESCRIPTION = 'OpenClaw timeline runtime with canonical timeline_resolve, LLM-based temporal reasoning, and guarded append-only writes.';
8
8
  exports.TIMELINE_TOOL_NAMES = ['timeline_resolve'];
9
9
  exports.TIMELINE_SKILL_PATHS = ['skills/timeline'];
@@ -33,12 +33,23 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.setOpenClawPluginRuntimeModuleLoaderForTests = setOpenClawPluginRuntimeModuleLoaderForTests;
37
+ exports.resetOpenClawPluginRuntimeModuleLoaderForTests = resetOpenClawPluginRuntimeModuleLoaderForTests;
36
38
  exports.makeOpenClawTimelineResolveToolFactory = makeOpenClawTimelineResolveToolFactory;
37
39
  const fs = __importStar(require("fs"));
38
40
  const path = __importStar(require("path"));
39
41
  const conversation_context_1 = require("./conversation_context");
40
42
  const time_utils_1 = require("../lib/time-utils");
41
43
  const timeline_resolve_1 = require("../tools/timeline_resolve");
44
+ let openClawPluginRuntimeModuleLoader = () => {
45
+ try {
46
+ return require('openclaw/plugin-sdk/runtime');
47
+ }
48
+ catch {
49
+ return null;
50
+ }
51
+ };
52
+ let cachedLateBoundGatewaySubagentRuntime;
42
53
  function readString(value) {
43
54
  return typeof value === 'string' && value.trim() ? value.trim() : undefined;
44
55
  }
@@ -67,6 +78,78 @@ function wrapToolPayload(payload) {
67
78
  details: payload,
68
79
  };
69
80
  }
81
+ function isSubagentRuntimeAvailable(runtime) {
82
+ return Boolean(runtime
83
+ && typeof runtime.run === 'function'
84
+ && typeof runtime.waitForRun === 'function'
85
+ && typeof runtime.getSessionMessages === 'function');
86
+ }
87
+ function isUnavailableSubagentRuntimeError(error) {
88
+ const message = error instanceof Error ? error.message : String(error);
89
+ return message.includes('Plugin runtime subagent methods are only available during a gateway request.');
90
+ }
91
+ function getLateBoundGatewaySubagentRuntime(pluginApi) {
92
+ if (cachedLateBoundGatewaySubagentRuntime !== undefined) {
93
+ return cachedLateBoundGatewaySubagentRuntime || undefined;
94
+ }
95
+ const runtimeModule = openClawPluginRuntimeModuleLoader();
96
+ const lateBoundRuntime = runtimeModule?.createPluginRuntime?.({
97
+ allowGatewaySubagentBinding: true,
98
+ });
99
+ const lateBoundSubagent = lateBoundRuntime?.subagent;
100
+ const subagentRuntime = isSubagentRuntimeAvailable(lateBoundSubagent)
101
+ ? lateBoundSubagent
102
+ : undefined;
103
+ if (!subagentRuntime) {
104
+ pluginApi.logger?.debug?.('timeline late-bound gateway subagent runtime unavailable');
105
+ }
106
+ cachedLateBoundGatewaySubagentRuntime = subagentRuntime ?? null;
107
+ return subagentRuntime;
108
+ }
109
+ async function withPreferredSubagentRuntime(pluginApi, purpose, execute) {
110
+ const injectedSubagent = pluginApi.runtime?.subagent;
111
+ const injectedRuntime = isSubagentRuntimeAvailable(injectedSubagent)
112
+ ? injectedSubagent
113
+ : undefined;
114
+ let injectedError;
115
+ if (injectedRuntime) {
116
+ try {
117
+ return await execute(injectedRuntime);
118
+ }
119
+ catch (error) {
120
+ injectedError = error;
121
+ if (!isUnavailableSubagentRuntimeError(error)) {
122
+ throw error;
123
+ }
124
+ pluginApi.logger?.debug?.(`timeline ${purpose} retrying with late-bound gateway subagent runtime`, {
125
+ error: error instanceof Error ? error.message : String(error),
126
+ });
127
+ }
128
+ }
129
+ const lateBoundRuntime = getLateBoundGatewaySubagentRuntime(pluginApi);
130
+ if (lateBoundRuntime && lateBoundRuntime !== injectedRuntime) {
131
+ return execute(lateBoundRuntime);
132
+ }
133
+ if (injectedError) {
134
+ throw injectedError;
135
+ }
136
+ throw new Error(`${purpose} dependency missing`);
137
+ }
138
+ function setOpenClawPluginRuntimeModuleLoaderForTests(loader) {
139
+ openClawPluginRuntimeModuleLoader = loader;
140
+ cachedLateBoundGatewaySubagentRuntime = undefined;
141
+ }
142
+ function resetOpenClawPluginRuntimeModuleLoaderForTests() {
143
+ openClawPluginRuntimeModuleLoader = () => {
144
+ try {
145
+ return require('openclaw/plugin-sdk/runtime');
146
+ }
147
+ catch {
148
+ return null;
149
+ }
150
+ };
151
+ cachedLateBoundGatewaySubagentRuntime = undefined;
152
+ }
70
153
  function resolveWorkspaceDir(pluginApi, toolContext) {
71
154
  if (toolContext.workspaceDir)
72
155
  return toolContext.workspaceDir;
@@ -616,61 +699,59 @@ function buildFallbackReasonerOutput(collector, error) {
616
699
  function createTimelineQueryPlanner(pluginApi, toolContext, runtimeConfig) {
617
700
  return async (input, anchor) => {
618
701
  try {
619
- const subagentRuntime = pluginApi.runtime?.subagent;
620
- if (!subagentRuntime?.run || !subagentRuntime.waitForRun || !subagentRuntime.getSessionMessages) {
621
- throw new Error('Timeline query planner dependency missing');
622
- }
623
702
  if (!String(input.query || '').trim()) {
624
703
  throw new Error('Timeline query planner dependency missing query');
625
704
  }
626
705
  const requestId = makePlannerRequestId();
627
706
  const baseSessionKey = toolContext.sessionKey || `plugin:${runtimeConfig.reasonerSessionPrefix}`;
628
707
  const plannerSessionKey = `${baseSessionKey}:${runtimeConfig.reasonerSessionPrefix}:planner:${requestId}`;
629
- try {
630
- const runResult = await subagentRuntime.run({
631
- sessionKey: plannerSessionKey,
632
- message: buildTimelineQueryPlannerMessage(input, anchor, requestId),
633
- extraSystemPrompt: buildTimelineQueryPlannerSystemPrompt(),
634
- deliver: false,
635
- idempotencyKey: requestId,
636
- });
637
- const waitResult = await subagentRuntime.waitForRun({
638
- runId: runResult.runId,
639
- timeoutMs: runtimeConfig.reasonerTimeoutMs,
640
- });
641
- if (waitResult.status === 'timeout') {
642
- throw new Error('Timeline query planner returned no decision');
643
- }
644
- if (waitResult.status === 'error') {
645
- throw new Error(waitResult.error || 'Timeline query planner returned no decision');
646
- }
647
- const session = await subagentRuntime.getSessionMessages({
648
- sessionKey: plannerSessionKey,
649
- limit: runtimeConfig.reasonerMessageLimit,
650
- });
651
- const jsonText = extractJsonObjectFromMessages(session.messages || [], 'Timeline query planner', requestId);
652
- const parsed = JSON.parse(jsonText);
653
- if (parsed.request_id && parsed.request_id !== requestId) {
654
- throw new Error('Timeline query planner returned mismatched request_id');
655
- }
656
- if (!['now', 'past_point', 'past_range'].includes(parsed.target_time_range)) {
657
- throw new Error('Timeline query planner returned an invalid target_time_range');
658
- }
659
- return parsed;
660
- }
661
- finally {
708
+ return await withPreferredSubagentRuntime(pluginApi, 'Timeline query planner', async (subagentRuntime) => {
662
709
  try {
663
- await subagentRuntime.deleteSession?.({
710
+ const runResult = await subagentRuntime.run({
664
711
  sessionKey: plannerSessionKey,
665
- deleteTranscript: true,
712
+ message: buildTimelineQueryPlannerMessage(input, anchor, requestId),
713
+ extraSystemPrompt: buildTimelineQueryPlannerSystemPrompt(),
714
+ deliver: false,
715
+ idempotencyKey: requestId,
666
716
  });
667
- }
668
- catch (error) {
669
- pluginApi.logger?.debug?.('timeline query planner session cleanup skipped', {
670
- error: error instanceof Error ? error.message : String(error),
717
+ const waitResult = await subagentRuntime.waitForRun({
718
+ runId: runResult.runId,
719
+ timeoutMs: runtimeConfig.reasonerTimeoutMs,
720
+ });
721
+ if (waitResult.status === 'timeout') {
722
+ throw new Error('Timeline query planner returned no decision');
723
+ }
724
+ if (waitResult.status === 'error') {
725
+ throw new Error(waitResult.error || 'Timeline query planner returned no decision');
726
+ }
727
+ const session = await subagentRuntime.getSessionMessages({
728
+ sessionKey: plannerSessionKey,
729
+ limit: runtimeConfig.reasonerMessageLimit,
671
730
  });
731
+ const jsonText = extractJsonObjectFromMessages(session.messages || [], 'Timeline query planner', requestId);
732
+ const parsed = JSON.parse(jsonText);
733
+ if (parsed.request_id && parsed.request_id !== requestId) {
734
+ throw new Error('Timeline query planner returned mismatched request_id');
735
+ }
736
+ if (!['now', 'past_point', 'past_range'].includes(parsed.target_time_range)) {
737
+ throw new Error('Timeline query planner returned an invalid target_time_range');
738
+ }
739
+ return parsed;
672
740
  }
673
- }
741
+ finally {
742
+ try {
743
+ await subagentRuntime.deleteSession?.({
744
+ sessionKey: plannerSessionKey,
745
+ deleteTranscript: true,
746
+ });
747
+ }
748
+ catch (error) {
749
+ pluginApi.logger?.debug?.('timeline query planner session cleanup skipped', {
750
+ error: error instanceof Error ? error.message : String(error),
751
+ });
752
+ }
753
+ }
754
+ });
674
755
  }
675
756
  catch (error) {
676
757
  pluginApi.logger?.warn?.('timeline query planner fallback engaged', {
@@ -751,50 +832,48 @@ function buildTimelineReasonerMessage(collector) {
751
832
  function createSubagentReasoner(pluginApi, toolContext, runtimeConfig) {
752
833
  return async (collector) => {
753
834
  try {
754
- const subagentRuntime = pluginApi.runtime?.subagent;
755
- if (!subagentRuntime?.run || !subagentRuntime.waitForRun || !subagentRuntime.getSessionMessages) {
756
- throw new Error('Timeline reasoner dependency missing');
757
- }
758
835
  const baseSessionKey = toolContext.sessionKey || `plugin:${runtimeConfig.reasonerSessionPrefix}`;
759
836
  const reasonerSessionKey = `${baseSessionKey}:${runtimeConfig.reasonerSessionPrefix}:${collector.request_id}`;
760
- try {
761
- const runResult = await subagentRuntime.run({
762
- sessionKey: reasonerSessionKey,
763
- message: buildTimelineReasonerMessage(collector),
764
- extraSystemPrompt: buildTimelineReasonerSystemPrompt(),
765
- deliver: false,
766
- idempotencyKey: collector.request_id,
767
- });
768
- const waitResult = await subagentRuntime.waitForRun({
769
- runId: runResult.runId,
770
- timeoutMs: runtimeConfig.reasonerTimeoutMs,
771
- });
772
- if (waitResult.status === 'timeout') {
773
- throw new Error('Timeline reasoner returned no decision');
774
- }
775
- if (waitResult.status === 'error') {
776
- throw new Error(waitResult.error || 'Timeline reasoner returned no decision');
777
- }
778
- const session = await subagentRuntime.getSessionMessages({
779
- sessionKey: reasonerSessionKey,
780
- limit: runtimeConfig.reasonerMessageLimit,
781
- });
782
- const jsonText = extractJsonObjectFromMessages(session.messages || [], 'Timeline reasoner', collector.request_id);
783
- return JSON.parse(jsonText);
784
- }
785
- finally {
837
+ return await withPreferredSubagentRuntime(pluginApi, 'Timeline reasoner', async (subagentRuntime) => {
786
838
  try {
787
- await subagentRuntime.deleteSession?.({
839
+ const runResult = await subagentRuntime.run({
788
840
  sessionKey: reasonerSessionKey,
789
- deleteTranscript: true,
841
+ message: buildTimelineReasonerMessage(collector),
842
+ extraSystemPrompt: buildTimelineReasonerSystemPrompt(),
843
+ deliver: false,
844
+ idempotencyKey: collector.request_id,
790
845
  });
791
- }
792
- catch (error) {
793
- pluginApi.logger?.debug?.('timeline reasoner session cleanup skipped', {
794
- error: error instanceof Error ? error.message : String(error),
846
+ const waitResult = await subagentRuntime.waitForRun({
847
+ runId: runResult.runId,
848
+ timeoutMs: runtimeConfig.reasonerTimeoutMs,
795
849
  });
850
+ if (waitResult.status === 'timeout') {
851
+ throw new Error('Timeline reasoner returned no decision');
852
+ }
853
+ if (waitResult.status === 'error') {
854
+ throw new Error(waitResult.error || 'Timeline reasoner returned no decision');
855
+ }
856
+ const session = await subagentRuntime.getSessionMessages({
857
+ sessionKey: reasonerSessionKey,
858
+ limit: runtimeConfig.reasonerMessageLimit,
859
+ });
860
+ const jsonText = extractJsonObjectFromMessages(session.messages || [], 'Timeline reasoner', collector.request_id);
861
+ return JSON.parse(jsonText);
796
862
  }
797
- }
863
+ finally {
864
+ try {
865
+ await subagentRuntime.deleteSession?.({
866
+ sessionKey: reasonerSessionKey,
867
+ deleteTranscript: true,
868
+ });
869
+ }
870
+ catch (error) {
871
+ pluginApi.logger?.debug?.('timeline reasoner session cleanup skipped', {
872
+ error: error instanceof Error ? error.message : String(error),
873
+ });
874
+ }
875
+ }
876
+ });
798
877
  }
799
878
  catch (error) {
800
879
  pluginApi.logger?.warn?.('timeline reasoner fallback engaged', {
@@ -819,14 +898,13 @@ function createTimelineResolveDependencies(pluginApi, toolContext) {
819
898
  }),
820
899
  sessionsHistory: async () => {
821
900
  const sessionKey = toolContext.sessionKey;
822
- const getSessionMessages = pluginApi.runtime?.subagent?.getSessionMessages;
823
- if (!sessionKey || !getSessionMessages)
901
+ if (!sessionKey)
824
902
  return [];
825
903
  try {
826
- const session = await getSessionMessages({
904
+ const session = await withPreferredSubagentRuntime(pluginApi, 'timeline sessionsHistory', (subagentRuntime) => subagentRuntime.getSessionMessages({
827
905
  sessionKey,
828
906
  limit: runtimeConfig.sessionHistoryLimit,
829
- });
907
+ }));
830
908
  return normalizeSessionHistory(session.messages || [], runtimeConfig.sessionHistoryLimit);
831
909
  }
832
910
  catch (error) {
@@ -838,8 +916,7 @@ function createTimelineResolveDependencies(pluginApi, toolContext) {
838
916
  },
839
917
  conversationContext: async (window, input) => {
840
918
  const sessionKey = toolContext.sessionKey;
841
- const getSessionMessages = pluginApi.runtime?.subagent?.getSessionMessages;
842
- if (!sessionKey || !getSessionMessages) {
919
+ if (!sessionKey) {
843
920
  return {
844
921
  is_recently_active: false,
845
922
  minutes_since_last_turn: null,
@@ -849,10 +926,10 @@ function createTimelineResolveDependencies(pluginApi, toolContext) {
849
926
  };
850
927
  }
851
928
  try {
852
- const session = await getSessionMessages({
929
+ const session = await withPreferredSubagentRuntime(pluginApi, 'timeline conversationContext', (subagentRuntime) => subagentRuntime.getSessionMessages({
853
930
  sessionKey,
854
931
  limit: runtimeConfig.sessionHistoryLimit,
855
- });
932
+ }));
856
933
  return (0, conversation_context_1.buildConversationContextFromMessages)(session.messages || [], window.end, input, runtimeConfig.conversationStickinessWindowMinutes, window.query_range);
857
934
  }
858
935
  catch (error) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "stella-timeline-plugin",
3
3
  "name": "Stella Timeline Plugin",
4
- "version": "2.0.6",
4
+ "version": "2.0.7",
5
5
  "description": "OpenClaw timeline runtime with canonical timeline_resolve, LLM-based temporal reasoning, and guarded append-only writes.",
6
6
  "entry": "dist/index.js",
7
7
  "skills": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stella-timeline-plugin",
3
- "version": "2.0.6",
3
+ "version": "2.0.7",
4
4
  "description": "Native OpenClaw timeline plugin with a canonical timeline_resolve tool, bundled skill routing, and guarded append-only writes.",
5
5
  "keywords": [
6
6
  "openclaw",
@@ -27,17 +27,20 @@ function syncPluginMetadataVersion() {
27
27
  const metadataSource = fs.readFileSync(metadataPath, 'utf8');
28
28
  const nextVersion = String(pkg.version);
29
29
 
30
+ const versionDeclRegex = /export const TIMELINE_PLUGIN_VERSION\s*=\s*'[^']*';/;
31
+ // If the regex doesn't match, don't silently proceed with a wrong release.
32
+ if (!versionDeclRegex.test(metadataSource)) {
33
+ throw new Error(`Unable to sync TIMELINE_PLUGIN_VERSION in ${metadataPath}`);
34
+ }
35
+
30
36
  const updatedSource = metadataSource.replace(
31
- /export const TIMELINE_PLUGIN_VERSION\s*=\s*'[^']*';/,
37
+ versionDeclRegex,
32
38
  `export const TIMELINE_PLUGIN_VERSION = '${nextVersion}';`,
33
39
  );
34
40
 
35
- // If the regex doesn't match, don't silently proceed with a wrong release.
36
- if (updatedSource === metadataSource) {
37
- throw new Error(`Unable to sync TIMELINE_PLUGIN_VERSION in ${metadataPath}`);
41
+ if (updatedSource !== metadataSource) {
42
+ fs.writeFileSync(metadataPath, `${updatedSource}\n`, 'utf8');
38
43
  }
39
-
40
- fs.writeFileSync(metadataPath, `${updatedSource}\n`, 'utf8');
41
44
  }
42
45
 
43
46
  function npmCommand() {