@ksm0709/context 0.0.19 → 0.0.20

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/dist/cli/index.js CHANGED
@@ -48,7 +48,7 @@ function resolveContextDir(projectDir) {
48
48
  // package.json
49
49
  var package_default = {
50
50
  name: "@ksm0709/context",
51
- version: "0.0.19",
51
+ version: "0.0.20",
52
52
  author: {
53
53
  name: "TaehoKang",
54
54
  email: "ksm07091@gmail.com"
@@ -56,7 +56,7 @@ var package_default = {
56
56
  type: "module",
57
57
  main: "./dist/index.js",
58
58
  bin: {
59
- context: "./dist/cli/index.js"
59
+ context: "dist/cli/index.js"
60
60
  },
61
61
  exports: {
62
62
  ".": {
@@ -72,7 +72,7 @@ var package_default = {
72
72
  },
73
73
  repository: {
74
74
  type: "git",
75
- url: "git@github.com:ksm0709/context.git"
75
+ url: "git+ssh://git@github.com/ksm0709/context.git"
76
76
  },
77
77
  publishConfig: {
78
78
  access: "public"
@@ -121,6 +121,12 @@ var DEFAULT_CONFIG = `{
121
121
  "knowledge": {
122
122
  "dir": "docs",
123
123
  "sources": ["AGENTS.md"]
124
+ },
125
+ "omx": {
126
+ // Inject turn-end after native turn-complete via tmux send-keys
127
+ "turnEnd": {
128
+ "strategy": "turn-complete-sendkeys"
129
+ }
124
130
  }
125
131
  }`;
126
132
  var DEFAULT_TURN_START = `## Knowledge Context
package/dist/index.js CHANGED
@@ -859,6 +859,11 @@ function getDefaultConfig() {
859
859
  mode: "auto",
860
860
  indexFilename: DEFAULTS.indexFilename,
861
861
  maxDomainDepth: DEFAULTS.maxDomainDepth
862
+ },
863
+ omx: {
864
+ turnEnd: {
865
+ strategy: "turn-complete-sendkeys"
866
+ }
862
867
  }
863
868
  };
864
869
  }
@@ -875,6 +880,11 @@ function mergeWithDefaults(partial) {
875
880
  mode: partial.knowledge?.mode ?? defaults.knowledge.mode,
876
881
  indexFilename: partial.knowledge?.indexFilename ?? defaults.knowledge.indexFilename,
877
882
  maxDomainDepth: partial.knowledge?.maxDomainDepth ?? defaults.knowledge.maxDomainDepth
883
+ },
884
+ omx: {
885
+ turnEnd: {
886
+ strategy: partial.omx?.turnEnd?.strategy ?? defaults.omx?.turnEnd?.strategy
887
+ }
878
888
  }
879
889
  };
880
890
  }
@@ -1113,7 +1123,7 @@ import { join as join4 } from "path";
1113
1123
  // package.json
1114
1124
  var package_default = {
1115
1125
  name: "@ksm0709/context",
1116
- version: "0.0.19",
1126
+ version: "0.0.20",
1117
1127
  author: {
1118
1128
  name: "TaehoKang",
1119
1129
  email: "ksm07091@gmail.com"
@@ -1121,7 +1131,7 @@ var package_default = {
1121
1131
  type: "module",
1122
1132
  main: "./dist/index.js",
1123
1133
  bin: {
1124
- context: "./dist/cli/index.js"
1134
+ context: "dist/cli/index.js"
1125
1135
  },
1126
1136
  exports: {
1127
1137
  ".": {
@@ -1137,7 +1147,7 @@ var package_default = {
1137
1147
  },
1138
1148
  repository: {
1139
1149
  type: "git",
1140
- url: "git@github.com:ksm0709/context.git"
1150
+ url: "git+ssh://git@github.com/ksm0709/context.git"
1141
1151
  },
1142
1152
  publishConfig: {
1143
1153
  access: "public"
@@ -1186,6 +1196,12 @@ var DEFAULT_CONFIG = `{
1186
1196
  "knowledge": {
1187
1197
  "dir": "docs",
1188
1198
  "sources": ["AGENTS.md"]
1199
+ },
1200
+ "omx": {
1201
+ // Inject turn-end after native turn-complete via tmux send-keys
1202
+ "turnEnd": {
1203
+ "strategy": "turn-complete-sendkeys"
1204
+ }
1189
1205
  }
1190
1206
  }`;
1191
1207
  var DEFAULT_TURN_START = `## Knowledge Context
@@ -1,5 +1,5 @@
1
1
  // src/omx/index.ts
2
- import { join as join5 } from "node:path";
2
+ import { isAbsolute, join as join5 } from "node:path";
3
3
 
4
4
  // src/constants.ts
5
5
  var DEFAULTS = {
@@ -55,6 +55,11 @@ function getDefaultConfig() {
55
55
  mode: "auto",
56
56
  indexFilename: DEFAULTS.indexFilename,
57
57
  maxDomainDepth: DEFAULTS.maxDomainDepth
58
+ },
59
+ omx: {
60
+ turnEnd: {
61
+ strategy: "turn-complete-sendkeys"
62
+ }
58
63
  }
59
64
  };
60
65
  }
@@ -71,6 +76,11 @@ function mergeWithDefaults(partial) {
71
76
  mode: partial.knowledge?.mode ?? defaults.knowledge.mode,
72
77
  indexFilename: partial.knowledge?.indexFilename ?? defaults.knowledge.indexFilename,
73
78
  maxDomainDepth: partial.knowledge?.maxDomainDepth ?? defaults.knowledge.maxDomainDepth
79
+ },
80
+ omx: {
81
+ turnEnd: {
82
+ strategy: partial.omx?.turnEnd?.strategy ?? defaults.omx?.turnEnd?.strategy
83
+ }
74
84
  }
75
85
  };
76
86
  }
@@ -309,7 +319,7 @@ import { join as join4 } from "node:path";
309
319
  // package.json
310
320
  var package_default = {
311
321
  name: "@ksm0709/context",
312
- version: "0.0.19",
322
+ version: "0.0.20",
313
323
  author: {
314
324
  name: "TaehoKang",
315
325
  email: "ksm07091@gmail.com"
@@ -317,7 +327,7 @@ var package_default = {
317
327
  type: "module",
318
328
  main: "./dist/index.js",
319
329
  bin: {
320
- context: "./dist/cli/index.js"
330
+ context: "dist/cli/index.js"
321
331
  },
322
332
  exports: {
323
333
  ".": {
@@ -333,7 +343,7 @@ var package_default = {
333
343
  },
334
344
  repository: {
335
345
  type: "git",
336
- url: "git@github.com:ksm0709/context.git"
346
+ url: "git+ssh://git@github.com/ksm0709/context.git"
337
347
  },
338
348
  publishConfig: {
339
349
  access: "public"
@@ -382,6 +392,12 @@ var DEFAULT_CONFIG = `{
382
392
  "knowledge": {
383
393
  "dir": "docs",
384
394
  "sources": ["AGENTS.md"]
395
+ },
396
+ "omx": {
397
+ // Inject turn-end after native turn-complete via tmux send-keys
398
+ "turnEnd": {
399
+ "strategy": "turn-complete-sendkeys"
400
+ }
385
401
  }
386
402
  }`;
387
403
  var DEFAULT_TURN_START = `## Knowledge Context
@@ -749,23 +765,91 @@ function injectIntoAgentsMd(agentsMdPath, content) {
749
765
  writeFileAtomically(agentsMdPath, nextContent);
750
766
  }
751
767
 
768
+ // src/omx/tmux-submit.ts
769
+ import { spawnSync } from "node:child_process";
770
+ function runTmux(args) {
771
+ const result = spawnSync("tmux", args, { encoding: "utf-8" });
772
+ if (result.error) {
773
+ return { ok: false, error: result.error.message };
774
+ }
775
+ if (result.status !== 0) {
776
+ return {
777
+ ok: false,
778
+ error: (result.stderr || "").trim() || `tmux exited ${result.status}`
779
+ };
780
+ }
781
+ return { ok: true };
782
+ }
783
+ function sleep(ms) {
784
+ return new Promise((resolve) => globalThis.setTimeout(resolve, ms));
785
+ }
786
+ async function sendTmuxSubmitSequence(target, attempts = 3) {
787
+ const totalAttempts = Math.max(1, Math.floor(attempts));
788
+ const delays = [180, 240, 320, 420];
789
+ for (let index = 0;index < totalAttempts; index += 1) {
790
+ const result = runTmux(["send-keys", "-t", target, "C-m"]);
791
+ if (!result.ok) {
792
+ return {
793
+ ok: false,
794
+ attempts: index + 1,
795
+ error: result.error
796
+ };
797
+ }
798
+ const delay = delays[index] ?? delays.at(-1) ?? 320;
799
+ await sleep(delay);
800
+ }
801
+ return {
802
+ ok: true,
803
+ attempts: totalAttempts
804
+ };
805
+ }
806
+
752
807
  // src/omx/index.ts
808
+ var TURN_END_STATE_KEY = "last_turn_end_turn_id";
809
+ var TURN_END_PENDING_SKIP_KEY = "turn_end_pending_followup_scopes";
753
810
  function resolveProjectDir(event) {
754
811
  return event.context?.projectDir ?? event.context?.directory ?? process.cwd();
755
812
  }
756
- async function onHookEvent(event, sdk) {
757
- if (event.event !== "session-start") {
813
+ function resolvePromptPath(directory, contextDir, promptPath) {
814
+ if (isAbsolute(promptPath))
815
+ return promptPath;
816
+ if (promptPath.startsWith(".context/") || promptPath.startsWith(".opencode/")) {
817
+ return join5(directory, promptPath);
818
+ }
819
+ return join5(directory, contextDir, promptPath);
820
+ }
821
+ function buildPromptVars(config) {
822
+ return { knowledgeDir: config.knowledge.dir ?? DEFAULTS.knowledgeDir };
823
+ }
824
+ function buildKnowledgeContent(projectDir, config) {
825
+ const knowledgeIndex = buildKnowledgeIndexV2(projectDir, config.knowledge);
826
+ return knowledgeIndex.mode === "flat" ? formatKnowledgeIndex(knowledgeIndex.individualFiles) : formatDomainIndex(knowledgeIndex);
827
+ }
828
+ function resolveFollowupScopeKey(event) {
829
+ if (event.session_id && event.session_id.trim().length > 0) {
830
+ return `session:${event.session_id.trim()}`;
831
+ }
832
+ if (event.thread_id && event.thread_id.trim().length > 0) {
833
+ return `thread:${event.thread_id.trim()}`;
834
+ }
835
+ return null;
836
+ }
837
+ async function logWarn(sdk, message, meta = {}) {
838
+ if (typeof sdk.log.warn === "function") {
839
+ await sdk.log.warn(message, meta);
758
840
  return;
759
841
  }
842
+ await sdk.log.info(message, meta);
843
+ }
844
+ async function onSessionStart(event, sdk) {
760
845
  const projectDir = resolveProjectDir(event);
761
846
  const contextDir = resolveContextDir(projectDir);
762
847
  scaffoldIfNeeded(projectDir);
763
848
  const config = loadConfig(projectDir);
764
- const promptVars = { knowledgeDir: config.knowledge.dir ?? DEFAULTS.knowledgeDir };
765
- const turnStartPath = join5(projectDir, contextDir, config.prompts.turnStart ?? join5("prompts", DEFAULTS.turnStartFile));
849
+ const promptVars = buildPromptVars(config);
850
+ const turnStartPath = resolvePromptPath(projectDir, contextDir, config.prompts.turnStart ?? join5(DEFAULTS.promptDir, DEFAULTS.turnStartFile));
766
851
  const turnStart = resolvePromptVariables(readPromptFile(turnStartPath), promptVars);
767
- const knowledgeIndex = buildKnowledgeIndexV2(projectDir, config.knowledge);
768
- const indexContent = knowledgeIndex.mode === "flat" ? formatKnowledgeIndex(knowledgeIndex.individualFiles) : formatDomainIndex(knowledgeIndex);
852
+ const indexContent = buildKnowledgeContent(projectDir, config);
769
853
  const combinedContent = [turnStart, indexContent].filter(Boolean).join(`
770
854
 
771
855
  `);
@@ -775,6 +859,155 @@ async function onHookEvent(event, sdk) {
775
859
  injectIntoAgentsMd(join5(projectDir, "AGENTS.md"), combinedContent);
776
860
  await sdk.log.info(`Injected context into AGENTS.md for ${projectDir}`);
777
861
  }
862
+ async function onTurnComplete(event, sdk) {
863
+ const projectDir = resolveProjectDir(event);
864
+ const contextDir = resolveContextDir(projectDir);
865
+ const config = loadConfig(projectDir);
866
+ const strategy = config.omx?.turnEnd?.strategy ?? "off";
867
+ if (strategy !== "turn-complete-sendkeys") {
868
+ return;
869
+ }
870
+ if (process.env.OMX_TEAM_WORKER) {
871
+ await logWarn(sdk, "turn_end_skipped_team_worker", {
872
+ event: event.event,
873
+ session_id: event.session_id,
874
+ turn_id: event.turn_id
875
+ });
876
+ return;
877
+ }
878
+ if (!event.turn_id) {
879
+ await logWarn(sdk, "turn_end_skipped_missing_turn_id", {
880
+ event: event.event,
881
+ session_id: event.session_id
882
+ });
883
+ return;
884
+ }
885
+ const followupScopeKey = resolveFollowupScopeKey(event);
886
+ const pendingFollowupScopes = typeof sdk.state?.read === "function" ? await sdk.state.read(TURN_END_PENDING_SKIP_KEY, {}) ?? {} : {};
887
+ if (followupScopeKey && pendingFollowupScopes[followupScopeKey]) {
888
+ const nextPendingScopes = { ...pendingFollowupScopes };
889
+ delete nextPendingScopes[followupScopeKey];
890
+ if (typeof sdk.state?.write === "function") {
891
+ await sdk.state.write(TURN_END_PENDING_SKIP_KEY, nextPendingScopes);
892
+ }
893
+ await sdk.log.info("turn_end_skipped_followup_turn", {
894
+ event: event.event,
895
+ session_id: event.session_id,
896
+ thread_id: event.thread_id,
897
+ turn_id: event.turn_id,
898
+ scope_key: followupScopeKey
899
+ });
900
+ return;
901
+ }
902
+ const lastTurnID = typeof sdk.state?.read === "function" ? await sdk.state.read(TURN_END_STATE_KEY) : undefined;
903
+ if (lastTurnID === event.turn_id) {
904
+ await sdk.log.info("turn_end_skipped_duplicate_turn", {
905
+ event: event.event,
906
+ session_id: event.session_id,
907
+ turn_id: event.turn_id
908
+ });
909
+ return;
910
+ }
911
+ const promptVars = buildPromptVars(config);
912
+ const turnEndPath = resolvePromptPath(projectDir, contextDir, config.prompts.turnEnd ?? join5(DEFAULTS.promptDir, DEFAULTS.turnEndFile));
913
+ const turnEndRaw = readPromptFile(turnEndPath);
914
+ if (!turnEndRaw) {
915
+ await sdk.log.info("turn_end_skipped_empty_prompt", {
916
+ event: event.event,
917
+ session_id: event.session_id,
918
+ turn_id: event.turn_id
919
+ });
920
+ return;
921
+ }
922
+ if (typeof sdk.tmux?.sendKeys !== "function") {
923
+ await logWarn(sdk, "turn_end_sendkeys_failed", {
924
+ event: event.event,
925
+ session_id: event.session_id,
926
+ turn_id: event.turn_id,
927
+ reason: "tmux_sendkeys_unavailable"
928
+ });
929
+ return;
930
+ }
931
+ const turnEnd = resolvePromptVariables(turnEndRaw, promptVars);
932
+ const reminderText = `<system-reminder>
933
+ ${turnEnd}
934
+ </system-reminder>`;
935
+ const sessionName = typeof event.context?.session_name === "string" && event.context.session_name.trim().length > 0 ? event.context.session_name.trim() : undefined;
936
+ const result = await sdk.tmux.sendKeys({
937
+ sessionName,
938
+ text: reminderText,
939
+ submit: false
940
+ });
941
+ if (!result.ok) {
942
+ await logWarn(sdk, "turn_end_sendkeys_failed", {
943
+ event: event.event,
944
+ session_id: event.session_id,
945
+ turn_id: event.turn_id,
946
+ reason: result.reason,
947
+ target: result.target,
948
+ error: result.error
949
+ });
950
+ return;
951
+ }
952
+ const submitTarget = result.paneId ?? result.target;
953
+ if (!submitTarget) {
954
+ await logWarn(sdk, "turn_end_sendkeys_failed", {
955
+ event: event.event,
956
+ session_id: event.session_id,
957
+ turn_id: event.turn_id,
958
+ reason: "missing_submit_target"
959
+ });
960
+ return;
961
+ }
962
+ const submitResult = await sendTmuxSubmitSequence(submitTarget);
963
+ if (!submitResult.ok) {
964
+ await logWarn(sdk, "turn_end_sendkeys_failed", {
965
+ event: event.event,
966
+ session_id: event.session_id,
967
+ turn_id: event.turn_id,
968
+ reason: "submit_sequence_failed",
969
+ target: submitTarget,
970
+ error: submitResult.error,
971
+ submit_attempts: submitResult.attempts
972
+ });
973
+ return;
974
+ }
975
+ if (typeof sdk.state?.write === "function") {
976
+ await sdk.state.write(TURN_END_STATE_KEY, event.turn_id);
977
+ if (followupScopeKey) {
978
+ await sdk.state.write(TURN_END_PENDING_SKIP_KEY, {
979
+ ...pendingFollowupScopes,
980
+ [followupScopeKey]: {
981
+ sourceTurnId: event.turn_id,
982
+ createdAt: Date.now()
983
+ }
984
+ });
985
+ }
986
+ }
987
+ await sdk.log.info("turn_end_submit_sequence_sent", {
988
+ event: event.event,
989
+ session_id: event.session_id,
990
+ turn_id: event.turn_id,
991
+ target: submitTarget,
992
+ submit_attempts: submitResult.attempts
993
+ });
994
+ await sdk.log.info("turn_end_sent", {
995
+ event: event.event,
996
+ session_id: event.session_id,
997
+ turn_id: event.turn_id,
998
+ target: result.target,
999
+ pane_id: result.paneId
1000
+ });
1001
+ }
1002
+ async function onHookEvent(event, sdk) {
1003
+ if (event.event === "session-start") {
1004
+ await onSessionStart(event, sdk);
1005
+ return;
1006
+ }
1007
+ if (event.event === "turn-complete") {
1008
+ await onTurnComplete(event, sdk);
1009
+ }
1010
+ }
778
1011
  export {
779
1012
  onHookEvent
780
1013
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ksm0709/context",
3
- "version": "0.0.19",
3
+ "version": "0.0.20",
4
4
  "author": {
5
5
  "name": "TaehoKang",
6
6
  "email": "ksm07091@gmail.com"
@@ -8,7 +8,7 @@
8
8
  "type": "module",
9
9
  "main": "./dist/index.js",
10
10
  "bin": {
11
- "context": "./dist/cli/index.js"
11
+ "context": "dist/cli/index.js"
12
12
  },
13
13
  "exports": {
14
14
  ".": {
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "repository": {
26
26
  "type": "git",
27
- "url": "git@github.com:ksm0709/context.git"
27
+ "url": "git+ssh://git@github.com/ksm0709/context.git"
28
28
  },
29
29
  "publishConfig": {
30
30
  "access": "public"