@ordewell/core 0.4.7 → 0.4.9

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/index.js CHANGED
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ ALLOWED_PLUGIN_HOSTS: () => ALLOWED_PLUGIN_HOSTS,
33
34
  ALL_PROVIDERS: () => ALL_PROVIDERS,
34
35
  AUTO_COMMANDS: () => AUTO_COMMANDS,
35
36
  AbstractRunner: () => AbstractRunner,
@@ -48,6 +49,8 @@ __export(index_exports, {
48
49
  CommandLineTooLongError: () => CommandLineTooLongError,
49
50
  ConsoleLogger: () => ConsoleLogger,
50
51
  ContextCollector: () => ContextCollector,
52
+ DAEMON_SUBPROTOCOL: () => DAEMON_SUBPROTOCOL,
53
+ DAEMON_TOKEN_SUBPROTOCOL_PREFIX: () => DAEMON_TOKEN_SUBPROTOCOL_PREFIX,
51
54
  DENY_ALL: () => DENY_ALL,
52
55
  EmbeddedNewlineError: () => EmbeddedNewlineError,
53
56
  EnvConfig: () => EnvConfig,
@@ -64,9 +67,12 @@ __export(index_exports, {
64
67
  ModelResolver: () => ModelResolver,
65
68
  OPENCODE_MANIFEST: () => OPENCODE_MANIFEST,
66
69
  ORCHESTRATOR_SHORTCUTS: () => ORCHESTRATOR_SHORTCUTS,
70
+ ORDEWELL_SETTABLE_ENV: () => ORDEWELL_SETTABLE_ENV,
67
71
  OpenAiService: () => OpenAiService,
68
72
  OpenCodeAdapter: () => OpenCodeAdapter,
69
73
  PLAN_ENVELOPE_KEY: () => PLAN_ENVELOPE_KEY,
74
+ PLUGIN_NAME_PATTERN: () => PLUGIN_NAME_PATTERN,
75
+ PROVIDER_CREDENTIAL_ENV: () => PROVIDER_CREDENTIAL_ENV,
70
76
  PROVIDER_DETECT_PRIORITY: () => PROVIDER_DETECT_PRIORITY,
71
77
  PROVIDER_LABEL: () => PROVIDER_LABEL,
72
78
  PROVIDER_PRIORITY: () => PROVIDER_PRIORITY,
@@ -81,6 +87,8 @@ __export(index_exports, {
81
87
  RunnerInstallation: () => RunnerInstallation,
82
88
  RunnerRegistry: () => RunnerRegistry,
83
89
  SEARCH_EXCLUSIONS: () => SEARCH_EXCLUSIONS,
90
+ SETTINGS_ENV_ALLOWLIST: () => SETTINGS_ENV_ALLOWLIST,
91
+ SETTINGS_ENV_REFUSED: () => SETTINGS_ENV_REFUSED,
84
92
  STATE_DIR: () => STATE_DIR,
85
93
  STOPPED_TOOL_RESULT: () => STOPPED_TOOL_RESULT,
86
94
  SYMBOL_LANGUAGES: () => SYMBOL_LANGUAGES,
@@ -93,13 +101,19 @@ __export(index_exports, {
93
101
  TmuxRunner: () => TmuxRunner,
94
102
  VerdictEngine: () => VerdictEngine,
95
103
  WINDOWS_MAX_COMMAND_LINE: () => WINDOWS_MAX_COMMAND_LINE,
104
+ WorkspaceNotAProjectError: () => WorkspaceNotAProjectError,
96
105
  WorkspaceNotFoundError: () => WorkspaceNotFoundError,
97
106
  addTaskToPlan: () => addTaskToPlan,
107
+ admitSettingsEnv: () => admitSettingsEnv,
98
108
  applyHeadLimit: () => applyHeadLimit,
99
109
  applyTaskOps: () => applyTaskOps,
110
+ assertInstallablePluginUrl: () => assertInstallablePluginUrl,
111
+ assertPlainPluginName: () => assertPlainPluginName,
100
112
  assertWorkspaceExists: () => assertWorkspaceExists,
113
+ assertWorkspaceIsProject: () => assertWorkspaceIsProject,
101
114
  augmentPromptWithPriorOutputs: () => augmentPromptWithPriorOutputs,
102
115
  augmentedPath: () => augmentedPath,
116
+ bearerHeaderValue: () => bearerHeaderValue,
103
117
  buildConversationSystemPrompt: () => buildConversationSystemPrompt,
104
118
  buildFallbackGrepArgs: () => buildFallbackGrepArgs,
105
119
  buildGlobArgs: () => buildGlobArgs,
@@ -127,7 +141,9 @@ __export(index_exports, {
127
141
  classifyCommand: () => classifyCommand,
128
142
  classifyOutcome: () => classifyOutcome,
129
143
  classifyPlannerReply: () => classifyPlannerReply,
144
+ classifyPluginSource: () => classifyPluginSource,
130
145
  clearAugmentedPathCache: () => clearAugmentedPathCache,
146
+ clearDaemonToken: () => clearDaemonToken,
131
147
  clearResearchShellCache: () => clearResearchShellCache,
132
148
  clearState: () => clearState,
133
149
  clipboardCopyCommand: () => clipboardCopyCommand,
@@ -139,6 +155,7 @@ __export(index_exports, {
139
155
  createAiService: () => createAiService,
140
156
  createEmptyPlan: () => createEmptyPlan,
141
157
  createTask: () => createTask,
158
+ daemonTokenPath: () => daemonTokenPath,
142
159
  defaultLogger: () => defaultLogger,
143
160
  definitionPattern: () => definitionPattern,
144
161
  deleteSession: () => deleteSession,
@@ -149,6 +166,7 @@ __export(index_exports, {
149
166
  emptyWarnings: () => emptyWarnings,
150
167
  enabledRunners: () => enabledRunners,
151
168
  ensureDir: () => ensureDir,
169
+ ensureStateDirIgnored: () => ensureStateDirIgnored,
152
170
  escapeControlCharsInStrings: () => escapeControlCharsInStrings,
153
171
  executionLogBlock: () => executionLogBlock,
154
172
  executionSummary: () => executionSummary,
@@ -156,6 +174,7 @@ __export(index_exports, {
156
174
  extractObjectWithBalance: () => extractObjectWithBalance,
157
175
  extractObjectsWithKey: () => extractObjectsWithKey,
158
176
  extractPrdBlock: () => extractPrdBlock,
177
+ extractPresentedToken: () => extractPresentedToken,
159
178
  fetchAllProviderModels: () => fetchAllProviderModels,
160
179
  filterFallbackByAnchoredInclude: () => filterFallbackByAnchoredInclude,
161
180
  filterModelsForPrompt: () => filterModelsForPrompt,
@@ -173,6 +192,9 @@ __export(index_exports, {
173
192
  isCliProvider: () => isCliProvider,
174
193
  isExecutableResolved: () => isExecutableResolved,
175
194
  isOpenAiProvider: () => isOpenAiProvider,
195
+ isPlainPluginName: () => isPlainPluginName,
196
+ isReservedRunnerName: () => isReservedRunnerName,
197
+ isValidManifest: () => isValidManifest,
176
198
  killTree: () => killTree,
177
199
  knownModelId: () => knownModelId,
178
200
  languageForId: () => languageForId,
@@ -185,6 +207,8 @@ __export(index_exports, {
185
207
  migrateLegacyPlan: () => migrateLegacyPlan,
186
208
  migratePlanState: () => migratePlanState,
187
209
  migrateTask: () => migrateTask,
210
+ mintDaemonToken: () => mintDaemonToken,
211
+ mintSessionId: () => mintSessionId,
188
212
  modelContextBlock: () => modelContextBlock,
189
213
  modifyValidationFeedback: () => modifyValidationFeedback,
190
214
  normalizeAgentArgs: () => normalizeAgentArgs,
@@ -200,6 +224,7 @@ __export(index_exports, {
200
224
  providerForRunner: () => providerForRunner,
201
225
  reEmitPlanPrompt: () => reEmitPlanPrompt,
202
226
  reEmitTaskOpsPrompt: () => reEmitTaskOpsPrompt,
227
+ readDaemonToken: () => readDaemonToken,
203
228
  referencePattern: () => referencePattern,
204
229
  removeTaskFromPlan: () => removeTaskFromPlan,
205
230
  renderPlanMap: () => renderPlanMap,
@@ -211,6 +236,7 @@ __export(index_exports, {
211
236
  resolveArgs: () => resolveArgs,
212
237
  resolveDefaultMode: () => resolveDefaultMode,
213
238
  resolveModelShortcut: () => resolveModelShortcut,
239
+ resolvePluginInstallDir: () => resolvePluginInstallDir,
214
240
  resolveProvider: () => resolveProvider,
215
241
  resolveProviderFromPrefix: () => resolveProviderFromPrefix,
216
242
  resolveResearchShell: () => resolveResearchShell,
@@ -241,6 +267,8 @@ __export(index_exports, {
241
267
  tmuxSocketName: () => tmuxSocketName,
242
268
  tmuxWindowName: () => tmuxWindowName,
243
269
  toOrchestratorOptions: () => toOrchestratorOptions,
270
+ tokenSubprotocols: () => tokenSubprotocols,
271
+ tokensMatch: () => tokensMatch,
244
272
  truncatedPlanReEmitPrompt: () => truncatedPlanReEmitPrompt,
245
273
  updateTaskInPlan: () => updateTaskInPlan,
246
274
  validateModifiedPlan: () => validateModifiedPlan,
@@ -605,7 +633,6 @@ var AUTO_COMMANDS = [
605
633
  "pwd",
606
634
  "which",
607
635
  "type",
608
- "env",
609
636
  "uname",
610
637
  "whoami",
611
638
  "cut",
@@ -616,13 +643,1233 @@ var AUTO_COMMANDS = [
616
643
  "jq",
617
644
  "yq"
618
645
  ];
646
+ var UNIVERSAL_FLAGS = ["--help", "--version"];
647
+ var COUNT_FLAG = /^-\d+$/;
648
+ var FLAG_POLICY = {
649
+ ls: {
650
+ cluster: true,
651
+ booleans: [
652
+ "-1",
653
+ "-a",
654
+ "-A",
655
+ "-b",
656
+ "-c",
657
+ "-C",
658
+ "-d",
659
+ "-D",
660
+ "-f",
661
+ "-F",
662
+ "-g",
663
+ "-G",
664
+ "-h",
665
+ "-H",
666
+ "-i",
667
+ "-k",
668
+ "-l",
669
+ "-L",
670
+ "-m",
671
+ "-n",
672
+ "-N",
673
+ "-o",
674
+ "-p",
675
+ "-q",
676
+ "-Q",
677
+ "-r",
678
+ "-R",
679
+ "-s",
680
+ "-S",
681
+ "-t",
682
+ "-u",
683
+ "-U",
684
+ "-v",
685
+ "-x",
686
+ "-X",
687
+ "-Z",
688
+ "--all",
689
+ "--almost-all",
690
+ "--author",
691
+ "--escape",
692
+ "--classify",
693
+ "--file-type",
694
+ "--full-time",
695
+ "--no-group",
696
+ "--human-readable",
697
+ "--si",
698
+ "--dereference-command-line",
699
+ "--inode",
700
+ "--kibibytes",
701
+ "--dereference",
702
+ "--numeric-uid-gid",
703
+ "--literal",
704
+ "--hide-control-chars",
705
+ "--show-control-chars",
706
+ "--quote-name",
707
+ "--reverse",
708
+ "--recursive",
709
+ "--size",
710
+ "--directory",
711
+ "--group-directories-first",
712
+ "--context",
713
+ "--zero"
714
+ ],
715
+ values: [
716
+ "-w",
717
+ "-I",
718
+ "-T",
719
+ "--width",
720
+ "--ignore",
721
+ "--hide",
722
+ "--block-size",
723
+ "--color",
724
+ "--format",
725
+ "--sort",
726
+ "--time",
727
+ "--time-style",
728
+ "--indicator-style",
729
+ "--quoting-style",
730
+ "--tabsize"
731
+ ]
732
+ },
733
+ tree: {
734
+ cluster: true,
735
+ // `-o` is absent on purpose: it is tree's write-to-a-file flag, which the
736
+ // redirect check never sees because there is no redirect.
737
+ booleans: [
738
+ "-a",
739
+ "-d",
740
+ "-f",
741
+ "-i",
742
+ "-l",
743
+ "-x",
744
+ "-C",
745
+ "-n",
746
+ "-p",
747
+ "-s",
748
+ "-h",
749
+ "-u",
750
+ "-g",
751
+ "-D",
752
+ "-t",
753
+ "-c",
754
+ "-r",
755
+ "-F",
756
+ "-q",
757
+ "-N",
758
+ "-Q",
759
+ "-v",
760
+ "-J",
761
+ "-X",
762
+ "-Z",
763
+ "--noreport",
764
+ "--dirsfirst",
765
+ "--du",
766
+ "--prune",
767
+ "--matchdirs",
768
+ "--inodes",
769
+ "--device",
770
+ "--si",
771
+ "--gitignore",
772
+ "--ignore-case",
773
+ "--filesfirst",
774
+ "--nolinks",
775
+ "--hintro"
776
+ ],
777
+ values: [
778
+ "-L",
779
+ "-P",
780
+ "-I",
781
+ "-H",
782
+ "--filelimit",
783
+ "--timefmt",
784
+ "--charset",
785
+ "--sort",
786
+ "--gitfile",
787
+ "--info",
788
+ "--prefix"
789
+ ]
790
+ },
791
+ git: {
792
+ cluster: true,
793
+ // Absent on purpose, and each for a reason that was demonstrated rather than
794
+ // guessed at:
795
+ // -c, --config-env set configuration inline, and several configuration
796
+ // keys name a program to run (`core.pager`, `core.editor`,
797
+ // `diff.external`), so this is an execution flag with one
798
+ // indirection in front of it.
799
+ // --exec-path redirects where git finds its helper programs; a
800
+ // fabricated helper ran during an ordinary remote listing.
801
+ // -O, --open-files-in-pager runs an arbitrary named program over search hits.
802
+ // --upload-pack, --receive-pack name the program run on the other end.
803
+ // --textconv, --ext-diff run filters the repository itself configures.
804
+ // --git-dir, --work-tree repoint git at a tree whose configuration is not
805
+ // the one the developer is looking at.
806
+ // -o, --output write a file. `git ls-files -o` is a legitimate spelling
807
+ // of `--others`, but `git archive -o` writes, and the
808
+ // long spelling stays available for the read.
809
+ booleans: [
810
+ "-a",
811
+ "-b",
812
+ "-E",
813
+ "-F",
814
+ "-h",
815
+ "-H",
816
+ "-i",
817
+ "-I",
818
+ "-l",
819
+ "-p",
820
+ "-P",
821
+ "-q",
822
+ "-r",
823
+ "-R",
824
+ "-s",
825
+ "-t",
826
+ "-u",
827
+ "-v",
828
+ "-w",
829
+ "-z",
830
+ "--oneline",
831
+ "--graph",
832
+ "--stat",
833
+ "--shortstat",
834
+ "--numstat",
835
+ "--dirstat",
836
+ "--summary",
837
+ "--name-only",
838
+ "--name-status",
839
+ "--raw",
840
+ "--patch",
841
+ "--no-patch",
842
+ "--patch-with-stat",
843
+ "--cached",
844
+ "--staged",
845
+ "--porcelain",
846
+ "--short",
847
+ "--long",
848
+ "--branch",
849
+ "--show-stash",
850
+ "--show-current",
851
+ "--all",
852
+ "--tags",
853
+ "--heads",
854
+ "--remotes",
855
+ "--list",
856
+ "--verbose",
857
+ "--quiet",
858
+ "--abbrev-ref",
859
+ "--count",
860
+ "--reverse",
861
+ "--first-parent",
862
+ "--no-merges",
863
+ "--merges",
864
+ "--follow",
865
+ "--full-history",
866
+ "--simplify-merges",
867
+ "--sparse",
868
+ "--dense",
869
+ "--ancestry-path",
870
+ "--left-right",
871
+ "--cherry",
872
+ "--cherry-pick",
873
+ "--cherry-mark",
874
+ "--boundary",
875
+ "--topo-order",
876
+ "--date-order",
877
+ "--author-date-order",
878
+ "--relative-date",
879
+ "--parents",
880
+ "--children",
881
+ "--timestamp",
882
+ "--no-walk",
883
+ "--do-walk",
884
+ "--decorate",
885
+ "--no-decorate",
886
+ "--abbrev-commit",
887
+ "--no-abbrev-commit",
888
+ "--abbrev",
889
+ "--no-abbrev",
890
+ "--color",
891
+ "--no-color",
892
+ "--pretty",
893
+ "--format",
894
+ "--expand-tabs",
895
+ "--no-expand-tabs",
896
+ "--numbered",
897
+ "--email",
898
+ "--no-notes",
899
+ "--notes",
900
+ "--show-signature",
901
+ "--find-renames",
902
+ "--find-copies",
903
+ "--find-copies-harder",
904
+ "--break-rewrites",
905
+ "--no-renames",
906
+ "--irreversible-delete",
907
+ "--full-index",
908
+ "--binary",
909
+ "--text",
910
+ "--check",
911
+ "--exit-code",
912
+ "--word-diff",
913
+ "--function-context",
914
+ "--ignore-all-space",
915
+ "--ignore-space-change",
916
+ "--ignore-space-at-eol",
917
+ "--ignore-blank-lines",
918
+ "--ignore-cr-at-eol",
919
+ "--no-prefix",
920
+ "--default-prefix",
921
+ "--no-textconv",
922
+ "--no-ext-diff",
923
+ "--submodule",
924
+ "--ignore-submodules",
925
+ "--recurse-submodules",
926
+ "--no-recurse-submodules",
927
+ "--others",
928
+ "--deleted",
929
+ "--modified",
930
+ "--unmerged",
931
+ "--stage",
932
+ "--killed",
933
+ "--ignored",
934
+ "--exclude-standard",
935
+ "--error-unmatch",
936
+ "--full-name",
937
+ "--directory",
938
+ "--no-empty-directory",
939
+ "--untracked-files",
940
+ "--no-optional-locks",
941
+ "--no-pager",
942
+ "--paginate",
943
+ "--bare",
944
+ "--literal-pathspecs",
945
+ "--glob-pathspecs",
946
+ "--noglob-pathspecs",
947
+ "--icase-pathspecs",
948
+ "--line-number",
949
+ "--column",
950
+ "--heading",
951
+ "--no-index",
952
+ "--untracked",
953
+ "--no-recursive",
954
+ "--name-rev",
955
+ "--root",
956
+ "--dirty",
957
+ "--broken",
958
+ "--always",
959
+ "--merged",
960
+ "--no-merged",
961
+ "--show-toplevel",
962
+ "--show-cdup",
963
+ "--git-common-dir",
964
+ "--absolute-git-dir",
965
+ "--is-inside-work-tree",
966
+ "--is-inside-git-dir",
967
+ "--is-bare-repository",
968
+ "--symbolic",
969
+ "--symbolic-full-name",
970
+ "--verify",
971
+ "--short-hash",
972
+ "--textual",
973
+ "--batch",
974
+ "--batch-check",
975
+ "--batch-all-objects",
976
+ "--buffer",
977
+ "--follow-symlinks",
978
+ "--use-mailmap"
979
+ ],
980
+ values: [
981
+ "-n",
982
+ "-C",
983
+ "-B",
984
+ "-M",
985
+ "-L",
986
+ "-S",
987
+ "-G",
988
+ "-U",
989
+ "-e",
990
+ "-m",
991
+ "-f",
992
+ "-g",
993
+ "-x",
994
+ "-X",
995
+ "-W",
996
+ "--max-count",
997
+ "--skip",
998
+ "--since",
999
+ "--after",
1000
+ "--until",
1001
+ "--before",
1002
+ "--author",
1003
+ "--grep",
1004
+ "--grep-reflog",
1005
+ "--committer",
1006
+ "--date",
1007
+ "--diff-filter",
1008
+ "--unified",
1009
+ "--stat-width",
1010
+ "--stat-name-width",
1011
+ "--stat-count",
1012
+ "--word-diff-regex",
1013
+ "--line-prefix",
1014
+ "--src-prefix",
1015
+ "--dst-prefix",
1016
+ "--pickaxe-regex",
1017
+ "--anchored",
1018
+ "--color-moved",
1019
+ "--color-words",
1020
+ "--inter-hunk-context",
1021
+ "--max-parents",
1022
+ "--min-parents",
1023
+ "--simplify-by-decoration",
1024
+ "--glob",
1025
+ "--exclude",
1026
+ "--sort",
1027
+ "--contains",
1028
+ "--points-at",
1029
+ "--decorate-refs",
1030
+ "--decorate-refs-exclude",
1031
+ "--encoding",
1032
+ "--context",
1033
+ "--max-depth",
1034
+ // `--filters` is absent from this list on purpose: it runs the clean and
1035
+ // smudge filters the repository under research configures, which is the
1036
+ // same execution vector as the configuration flag above with a different
1037
+ // spelling.
1038
+ "--and",
1039
+ "--or",
1040
+ "--not",
1041
+ "--all-match",
1042
+ "--threads",
1043
+ "--path"
1044
+ ],
1045
+ patterns: [COUNT_FLAG]
1046
+ },
1047
+ wc: {
1048
+ cluster: true,
1049
+ booleans: [
1050
+ "-c",
1051
+ "-l",
1052
+ "-m",
1053
+ "-w",
1054
+ "-L",
1055
+ "--bytes",
1056
+ "--chars",
1057
+ "--lines",
1058
+ "--words",
1059
+ "--max-line-length",
1060
+ "--total"
1061
+ ]
1062
+ },
1063
+ du: {
1064
+ cluster: true,
1065
+ booleans: [
1066
+ "-0",
1067
+ "-a",
1068
+ "-b",
1069
+ "-c",
1070
+ "-D",
1071
+ "-h",
1072
+ "-H",
1073
+ "-k",
1074
+ "-l",
1075
+ "-L",
1076
+ "-m",
1077
+ "-P",
1078
+ "-s",
1079
+ "-S",
1080
+ "-x",
1081
+ "--all",
1082
+ "--apparent-size",
1083
+ "--bytes",
1084
+ "--total",
1085
+ "--dereference-args",
1086
+ "--human-readable",
1087
+ "--si",
1088
+ "--inodes",
1089
+ "--count-links",
1090
+ "--dereference",
1091
+ "--no-dereference",
1092
+ "--separate-dirs",
1093
+ "--summarize",
1094
+ "--one-file-system",
1095
+ "--null"
1096
+ ],
1097
+ values: [
1098
+ "-d",
1099
+ "-t",
1100
+ "-B",
1101
+ "-X",
1102
+ "--max-depth",
1103
+ "--threshold",
1104
+ "--block-size",
1105
+ "--exclude",
1106
+ "--exclude-from",
1107
+ "--time",
1108
+ "--time-style",
1109
+ "--files0-from"
1110
+ ]
1111
+ },
1112
+ df: {
1113
+ cluster: true,
1114
+ booleans: [
1115
+ "-a",
1116
+ "-h",
1117
+ "-H",
1118
+ "-i",
1119
+ "-k",
1120
+ "-l",
1121
+ "-m",
1122
+ "-P",
1123
+ "-T",
1124
+ "-v",
1125
+ "--all",
1126
+ "--human-readable",
1127
+ "--si",
1128
+ "--inodes",
1129
+ "--local",
1130
+ "--portability",
1131
+ "--print-type",
1132
+ "--total",
1133
+ "--sync",
1134
+ "--no-sync"
1135
+ ],
1136
+ values: ["-B", "-t", "-x", "--block-size", "--type", "--exclude-type"]
1137
+ },
1138
+ file: {
1139
+ cluster: true,
1140
+ // `-C`/`--compile` is absent: it writes a compiled magic file.
1141
+ booleans: [
1142
+ "-b",
1143
+ "-c",
1144
+ "-h",
1145
+ "-i",
1146
+ "-k",
1147
+ "-L",
1148
+ "-n",
1149
+ "-N",
1150
+ "-p",
1151
+ "-r",
1152
+ "-s",
1153
+ "-S",
1154
+ "-v",
1155
+ "-z",
1156
+ "-0",
1157
+ "--brief",
1158
+ "--mime",
1159
+ "--mime-type",
1160
+ "--mime-encoding",
1161
+ "--dereference",
1162
+ "--no-dereference",
1163
+ "--keep-going",
1164
+ "--no-buffer",
1165
+ "--no-pad",
1166
+ "--raw",
1167
+ "--special-files",
1168
+ "--uncompress",
1169
+ "--print0",
1170
+ "--checking-printout"
1171
+ ],
1172
+ values: [
1173
+ "-e",
1174
+ "-m",
1175
+ "-P",
1176
+ "-F",
1177
+ "--exclude",
1178
+ "--magic-file",
1179
+ "--separator",
1180
+ "--parameter",
1181
+ "--extension",
1182
+ "--files-from"
1183
+ ]
1184
+ },
1185
+ head: {
1186
+ cluster: true,
1187
+ booleans: ["-q", "-v", "-z", "--quiet", "--silent", "--verbose", "--zero-terminated"],
1188
+ values: ["-c", "-n", "--bytes", "--lines"],
1189
+ patterns: [COUNT_FLAG]
1190
+ },
1191
+ tail: {
1192
+ cluster: true,
1193
+ booleans: [
1194
+ "-f",
1195
+ "-F",
1196
+ "-q",
1197
+ "-v",
1198
+ "-z",
1199
+ "--follow",
1200
+ "--retry",
1201
+ "--quiet",
1202
+ "--silent",
1203
+ "--verbose",
1204
+ "--zero-terminated"
1205
+ ],
1206
+ values: [
1207
+ "-c",
1208
+ "-n",
1209
+ "-s",
1210
+ "--bytes",
1211
+ "--lines",
1212
+ "--sleep-interval",
1213
+ "--pid",
1214
+ "--max-unchanged-stats"
1215
+ ],
1216
+ patterns: [COUNT_FLAG]
1217
+ },
1218
+ cat: {
1219
+ cluster: true,
1220
+ booleans: [
1221
+ "-A",
1222
+ "-b",
1223
+ "-e",
1224
+ "-E",
1225
+ "-n",
1226
+ "-s",
1227
+ "-t",
1228
+ "-T",
1229
+ "-u",
1230
+ "-v",
1231
+ "--show-all",
1232
+ "--number-nonblank",
1233
+ "--show-ends",
1234
+ "--number",
1235
+ "--squeeze-blank",
1236
+ "--show-tabs",
1237
+ "--show-nonprinting"
1238
+ ]
1239
+ },
1240
+ sort: {
1241
+ cluster: true,
1242
+ // Absent on purpose: `-o`/`--output` writes a file with no redirect for the
1243
+ // redirect check to see, and `--compress-program` names a program that sort
1244
+ // runs over its own temporary files — a permitted binary as an interpreter.
1245
+ booleans: [
1246
+ "-b",
1247
+ "-c",
1248
+ "-C",
1249
+ "-d",
1250
+ "-f",
1251
+ "-g",
1252
+ "-h",
1253
+ "-i",
1254
+ "-M",
1255
+ "-n",
1256
+ "-r",
1257
+ "-R",
1258
+ "-s",
1259
+ "-u",
1260
+ "-V",
1261
+ "-z",
1262
+ "--ignore-leading-blanks",
1263
+ "--check",
1264
+ "--dictionary-order",
1265
+ "--ignore-case",
1266
+ "--general-numeric-sort",
1267
+ "--human-numeric-sort",
1268
+ "--ignore-nonprinting",
1269
+ "--month-sort",
1270
+ "--numeric-sort",
1271
+ "--random-sort",
1272
+ "--reverse",
1273
+ "--stable",
1274
+ "--unique",
1275
+ "--version-sort",
1276
+ "--zero-terminated",
1277
+ "--debug"
1278
+ ],
1279
+ values: [
1280
+ "-k",
1281
+ "-t",
1282
+ "-S",
1283
+ "-T",
1284
+ "--key",
1285
+ "--field-separator",
1286
+ "--buffer-size",
1287
+ "--temporary-directory",
1288
+ "--random-source",
1289
+ "--batch-size",
1290
+ "--parallel",
1291
+ "--sort"
1292
+ ]
1293
+ },
1294
+ uniq: {
1295
+ cluster: true,
1296
+ booleans: [
1297
+ "-c",
1298
+ "-d",
1299
+ "-D",
1300
+ "-i",
1301
+ "-u",
1302
+ "-z",
1303
+ "--count",
1304
+ "--repeated",
1305
+ "--all-repeated",
1306
+ "--ignore-case",
1307
+ "--unique",
1308
+ "--zero-terminated"
1309
+ ],
1310
+ values: ["-f", "-s", "-w", "--skip-fields", "--skip-chars", "--check-chars", "--group"]
1311
+ },
1312
+ // No flag on either runs a program or writes a file, and the planner's
1313
+ // commonest use of `echo` is a section marker — `echo --- files changed ---`
1314
+ // is three tokens that all begin with a dash and none of them is a flag.
1315
+ echo: { freeform: true },
1316
+ printf: { freeform: true },
1317
+ date: {
1318
+ cluster: true,
1319
+ // `-s`/`--set` is absent: it sets the system clock.
1320
+ booleans: [
1321
+ "-u",
1322
+ "-R",
1323
+ "-I",
1324
+ "--utc",
1325
+ "--universal",
1326
+ "--rfc-email",
1327
+ "--rfc-3339",
1328
+ "--iso-8601",
1329
+ "--debug"
1330
+ ],
1331
+ values: ["-d", "-f", "-r", "--date", "--file", "--reference"]
1332
+ },
1333
+ stat: {
1334
+ cluster: true,
1335
+ booleans: ["-f", "-L", "-t", "--file-system", "--terse", "--dereference"],
1336
+ values: ["-c", "--format", "--printf", "--cached"]
1337
+ },
1338
+ basename: {
1339
+ cluster: true,
1340
+ booleans: ["-a", "-z", "--multiple", "--zero"],
1341
+ values: ["-s", "--suffix"]
1342
+ },
1343
+ dirname: {
1344
+ cluster: true,
1345
+ booleans: ["-z", "--zero"]
1346
+ },
1347
+ realpath: {
1348
+ cluster: true,
1349
+ booleans: [
1350
+ "-e",
1351
+ "-m",
1352
+ "-L",
1353
+ "-P",
1354
+ "-q",
1355
+ "-s",
1356
+ "-z",
1357
+ "--canonicalize-existing",
1358
+ "--canonicalize-missing",
1359
+ "--logical",
1360
+ "--physical",
1361
+ "--quiet",
1362
+ "--strip",
1363
+ "--no-symlinks",
1364
+ "--zero"
1365
+ ],
1366
+ values: ["--relative-to", "--relative-base"]
1367
+ },
1368
+ pwd: { cluster: true, booleans: ["-L", "-P", "--logical", "--physical"] },
1369
+ which: { cluster: true, booleans: ["-a", "-s", "--all"] },
1370
+ type: { cluster: true, booleans: ["-a", "-f", "-p", "-P", "-t"] },
1371
+ uname: {
1372
+ cluster: true,
1373
+ booleans: [
1374
+ "-a",
1375
+ "-i",
1376
+ "-m",
1377
+ "-n",
1378
+ "-o",
1379
+ "-p",
1380
+ "-r",
1381
+ "-s",
1382
+ "-v",
1383
+ "--all",
1384
+ "--kernel-name",
1385
+ "--nodename",
1386
+ "--kernel-release",
1387
+ "--kernel-version",
1388
+ "--machine",
1389
+ "--processor",
1390
+ "--hardware-platform",
1391
+ "--operating-system"
1392
+ ]
1393
+ },
1394
+ whoami: {},
1395
+ cut: {
1396
+ cluster: true,
1397
+ // `--output-delimiter` names a separator string, not a file.
1398
+ booleans: ["-n", "-s", "-z", "--only-delimited", "--complement", "--zero-terminated"],
1399
+ values: [
1400
+ "-b",
1401
+ "-c",
1402
+ "-d",
1403
+ "-f",
1404
+ "--bytes",
1405
+ "--characters",
1406
+ "--delimiter",
1407
+ "--fields",
1408
+ "--output-delimiter"
1409
+ ]
1410
+ },
1411
+ nl: {
1412
+ cluster: true,
1413
+ booleans: ["-p", "--no-renumber"],
1414
+ values: [
1415
+ "-b",
1416
+ "-d",
1417
+ "-f",
1418
+ "-h",
1419
+ "-i",
1420
+ "-l",
1421
+ "-n",
1422
+ "-s",
1423
+ "-v",
1424
+ "-w",
1425
+ "--body-numbering",
1426
+ "--section-delimiter",
1427
+ "--footer-numbering",
1428
+ "--header-numbering",
1429
+ "--join-blank-lines",
1430
+ "--line-increment",
1431
+ "--number-format",
1432
+ "--number-separator",
1433
+ "--starting-line-number",
1434
+ "--number-width"
1435
+ ]
1436
+ },
1437
+ rg: {
1438
+ cluster: true,
1439
+ // `--pre` runs a named preprocessor over every file searched and
1440
+ // `--hostname-bin` runs a named program to resolve the hostname; both are
1441
+ // absent, and `--pre-glob` with them.
1442
+ booleans: [
1443
+ "-a",
1444
+ "-c",
1445
+ "-F",
1446
+ "-h",
1447
+ "-H",
1448
+ "-i",
1449
+ "-I",
1450
+ "-l",
1451
+ "-L",
1452
+ "-n",
1453
+ "-N",
1454
+ "-o",
1455
+ "-p",
1456
+ "-P",
1457
+ "-q",
1458
+ "-s",
1459
+ "-S",
1460
+ "-u",
1461
+ "-uu",
1462
+ "-uuu",
1463
+ "-U",
1464
+ "-v",
1465
+ "-V",
1466
+ "-w",
1467
+ "-x",
1468
+ "-z",
1469
+ "--text",
1470
+ "--count",
1471
+ "--count-matches",
1472
+ "--fixed-strings",
1473
+ "--files",
1474
+ "--files-with-matches",
1475
+ "--files-without-match",
1476
+ "--follow",
1477
+ "--hidden",
1478
+ "--no-hidden",
1479
+ "--ignore-case",
1480
+ "--case-sensitive",
1481
+ "--smart-case",
1482
+ "--invert-match",
1483
+ "--line-number",
1484
+ "--no-line-number",
1485
+ "--with-filename",
1486
+ "--no-filename",
1487
+ "--heading",
1488
+ "--no-heading",
1489
+ "--column",
1490
+ "--no-column",
1491
+ "--byte-offset",
1492
+ "--vimgrep",
1493
+ "--json",
1494
+ "--stats",
1495
+ "--trim",
1496
+ "--pretty",
1497
+ "--passthru",
1498
+ "--word-regexp",
1499
+ "--line-regexp",
1500
+ "--multiline",
1501
+ "--multiline-dotall",
1502
+ "--no-multiline",
1503
+ "--crlf",
1504
+ "--null",
1505
+ "--null-data",
1506
+ "--binary",
1507
+ "--search-zip",
1508
+ "--no-ignore",
1509
+ "--no-ignore-dot",
1510
+ "--no-ignore-exclude",
1511
+ "--no-ignore-files",
1512
+ "--no-ignore-global",
1513
+ "--no-ignore-parent",
1514
+ "--no-ignore-vcs",
1515
+ "--no-ignore-messages",
1516
+ "--no-messages",
1517
+ "--no-require-git",
1518
+ "--require-git",
1519
+ "--one-file-system",
1520
+ "--pcre2",
1521
+ "--no-pcre2",
1522
+ "--auto-hybrid-regex",
1523
+ "--line-buffered",
1524
+ "--block-buffered",
1525
+ "--unicode",
1526
+ "--no-unicode",
1527
+ "--unrestricted",
1528
+ "--type-list",
1529
+ "--debug",
1530
+ "--trace",
1531
+ "--include-zero",
1532
+ "--no-context-separator",
1533
+ "--stop-on-nonmatch",
1534
+ "--quiet",
1535
+ "--no-binary"
1536
+ ],
1537
+ values: [
1538
+ "-A",
1539
+ "-B",
1540
+ "-C",
1541
+ "-d",
1542
+ "-e",
1543
+ "-E",
1544
+ "-f",
1545
+ "-g",
1546
+ "-j",
1547
+ "-m",
1548
+ "-M",
1549
+ "-r",
1550
+ "-t",
1551
+ "-T",
1552
+ "--after-context",
1553
+ "--before-context",
1554
+ "--context",
1555
+ "--context-separator",
1556
+ "--field-context-separator",
1557
+ "--field-match-separator",
1558
+ "--regexp",
1559
+ "--file",
1560
+ "--glob",
1561
+ "--iglob",
1562
+ "--glob-case-insensitive",
1563
+ "--type",
1564
+ "--type-not",
1565
+ "--type-add",
1566
+ "--type-clear",
1567
+ "--max-count",
1568
+ "--max-columns",
1569
+ "--max-depth",
1570
+ "--maxdepth",
1571
+ "--max-filesize",
1572
+ "--replace",
1573
+ "--threads",
1574
+ "--encoding",
1575
+ "--engine",
1576
+ "--sort",
1577
+ "--sortr",
1578
+ "--color",
1579
+ "--colors",
1580
+ "--path-separator",
1581
+ "--ignore-file",
1582
+ "--dfa-size-limit",
1583
+ "--regex-size-limit",
1584
+ "--binary-files",
1585
+ "--max-columns-preview"
1586
+ ]
1587
+ },
1588
+ grep: {
1589
+ cluster: true,
1590
+ // `-o` here is `--only-matching`, which writes nothing. It is the same two
1591
+ // characters as `sort -o`, which writes a file — the reason these sets are
1592
+ // per-binary rather than one shared list.
1593
+ booleans: [
1594
+ "-a",
1595
+ "-b",
1596
+ "-c",
1597
+ "-E",
1598
+ "-F",
1599
+ "-G",
1600
+ "-H",
1601
+ "-h",
1602
+ "-i",
1603
+ "-I",
1604
+ "-l",
1605
+ "-L",
1606
+ "-n",
1607
+ "-o",
1608
+ "-P",
1609
+ "-q",
1610
+ "-r",
1611
+ "-R",
1612
+ "-s",
1613
+ "-T",
1614
+ "-U",
1615
+ "-v",
1616
+ "-w",
1617
+ "-x",
1618
+ "-y",
1619
+ "-z",
1620
+ "-Z",
1621
+ "--extended-regexp",
1622
+ "--fixed-strings",
1623
+ "--basic-regexp",
1624
+ "--perl-regexp",
1625
+ "--ignore-case",
1626
+ "--no-ignore-case",
1627
+ "--invert-match",
1628
+ "--word-regexp",
1629
+ "--line-regexp",
1630
+ "--count",
1631
+ "--files-with-matches",
1632
+ "--files-without-match",
1633
+ "--line-number",
1634
+ "--with-filename",
1635
+ "--no-filename",
1636
+ "--only-matching",
1637
+ "--quiet",
1638
+ "--silent",
1639
+ "--no-messages",
1640
+ "--text",
1641
+ "--binary",
1642
+ "--line-buffered",
1643
+ "--null",
1644
+ "--null-data",
1645
+ "--byte-offset",
1646
+ "--recursive",
1647
+ "--dereference-recursive",
1648
+ "--initial-tab",
1649
+ "--unix-byte-offsets",
1650
+ "--no-group-separator",
1651
+ "--color",
1652
+ "--colour"
1653
+ ],
1654
+ values: [
1655
+ "-A",
1656
+ "-B",
1657
+ "-C",
1658
+ "-d",
1659
+ "-D",
1660
+ "-e",
1661
+ "-f",
1662
+ "-m",
1663
+ "--after-context",
1664
+ "--before-context",
1665
+ "--context",
1666
+ "--regexp",
1667
+ "--file",
1668
+ "--max-count",
1669
+ "--devices",
1670
+ "--directories",
1671
+ "--binary-files",
1672
+ "--include",
1673
+ "--exclude",
1674
+ "--exclude-dir",
1675
+ "--exclude-from",
1676
+ "--label",
1677
+ "--group-separator"
1678
+ ]
1679
+ },
1680
+ find: {
1681
+ // No clustering: find spells its flags as single-dash words, so walking the
1682
+ // characters of `-name` would read it as five short flags. And its arguments
1683
+ // are an expression, so `--` does not end them.
1684
+ expressionArgs: true,
1685
+ // Absent on purpose: `-exec`, `-execdir`, `-ok` and `-okdir` run an inner
1686
+ // command (also caught by the named guard below, which says so more
1687
+ // precisely), `-delete` removes files, and `-fprint`, `-fprint0`, `-fprintf`
1688
+ // and `-fls` are find's write family — the same write as `> file` with no
1689
+ // redirect for the redirect check to see.
1690
+ booleans: [
1691
+ "-H",
1692
+ "-L",
1693
+ "-P",
1694
+ "-a",
1695
+ "-and",
1696
+ "-o",
1697
+ "-or",
1698
+ "-not",
1699
+ "-d",
1700
+ "-depth",
1701
+ "-daystart",
1702
+ "-empty",
1703
+ "-executable",
1704
+ "-false",
1705
+ "-follow",
1706
+ "-help",
1707
+ "--help",
1708
+ "-ignore_readdir_race",
1709
+ "-noignore_readdir_race",
1710
+ "-ls",
1711
+ "-mount",
1712
+ "-nogroup",
1713
+ "-noleaf",
1714
+ "-nouser",
1715
+ "-print",
1716
+ "-print0",
1717
+ "-prune",
1718
+ "-quit",
1719
+ "-readable",
1720
+ "-true",
1721
+ "-version",
1722
+ "-writable",
1723
+ "-xdev"
1724
+ ],
1725
+ values: [
1726
+ "-maxdepth",
1727
+ "-mindepth",
1728
+ "-name",
1729
+ "-iname",
1730
+ "-path",
1731
+ "-ipath",
1732
+ "-wholename",
1733
+ "-iwholename",
1734
+ "-regex",
1735
+ "-iregex",
1736
+ "-lname",
1737
+ "-ilname",
1738
+ "-type",
1739
+ "-xtype",
1740
+ "-size",
1741
+ "-newer",
1742
+ "-anewer",
1743
+ "-cnewer",
1744
+ "-mtime",
1745
+ "-mmin",
1746
+ "-ctime",
1747
+ "-cmin",
1748
+ "-atime",
1749
+ "-amin",
1750
+ "-used",
1751
+ "-uid",
1752
+ "-gid",
1753
+ "-user",
1754
+ "-group",
1755
+ "-perm",
1756
+ "-links",
1757
+ "-inum",
1758
+ "-samefile",
1759
+ "-fstype",
1760
+ "-regextype",
1761
+ "-files0-from",
1762
+ "-printf",
1763
+ "-context",
1764
+ "-D"
1765
+ ],
1766
+ // `find -O2` glues the optimisation level on, and `-newermt`/`-newerat` and
1767
+ // friends spell the two comparison fields into the flag name.
1768
+ patterns: [/^-O\d$/, /^-newer[acmBt][acmBt]$/]
1769
+ },
1770
+ jq: {
1771
+ cluster: true,
1772
+ booleans: [
1773
+ "-a",
1774
+ "-c",
1775
+ "-C",
1776
+ "-e",
1777
+ "-h",
1778
+ "-j",
1779
+ "-M",
1780
+ "-n",
1781
+ "-r",
1782
+ "-R",
1783
+ "-s",
1784
+ "-S",
1785
+ "--ascii-output",
1786
+ "--compact-output",
1787
+ "--color-output",
1788
+ "--monochrome-output",
1789
+ "--exit-status",
1790
+ "--join-output",
1791
+ "--null-input",
1792
+ "--raw-input",
1793
+ "--raw-output",
1794
+ "--raw-output0",
1795
+ "--slurp",
1796
+ "--sort-keys",
1797
+ "--seq",
1798
+ "--stream",
1799
+ "--stream-errors",
1800
+ "--tab",
1801
+ "--unbuffered",
1802
+ "--args",
1803
+ "--jsonargs"
1804
+ ],
1805
+ values: [
1806
+ "-f",
1807
+ "-L",
1808
+ "--arg",
1809
+ "--argjson",
1810
+ "--slurpfile",
1811
+ "--rawfile",
1812
+ "--indent",
1813
+ "--from-file"
1814
+ ]
1815
+ },
1816
+ yq: {
1817
+ cluster: true,
1818
+ // `-i`/`--inplace`/`--in-place` rewrite the file, and `-s`/`--split-exp`
1819
+ // writes one file per document. All absent — and all bare or near-bare
1820
+ // booleans, which is why exempting booleans was not an option.
1821
+ booleans: [
1822
+ "-C",
1823
+ "-e",
1824
+ "-h",
1825
+ "-j",
1826
+ "-M",
1827
+ "-n",
1828
+ "-N",
1829
+ "-P",
1830
+ "-r",
1831
+ "-S",
1832
+ "-v",
1833
+ "-y",
1834
+ "-Y",
1835
+ "--colors",
1836
+ "--no-colors",
1837
+ "--exit-status",
1838
+ "--no-doc",
1839
+ "--null-input",
1840
+ "--prettyPrint",
1841
+ "--raw-output",
1842
+ "--sort-keys",
1843
+ "--tojson",
1844
+ "--yaml-output",
1845
+ "--yaml-roundtrip",
1846
+ "--unwrapScalar",
1847
+ "--header-preprocess",
1848
+ "--no-exit-status",
1849
+ "--verbose",
1850
+ "--xml-strict-mode"
1851
+ ],
1852
+ values: [
1853
+ "-I",
1854
+ "-o",
1855
+ "-p",
1856
+ "-L",
1857
+ "--indent",
1858
+ "--output-format",
1859
+ "--input-format",
1860
+ "--expression",
1861
+ "--width",
1862
+ "--xml-attribute-prefix",
1863
+ "--xml-content-name"
1864
+ ]
1865
+ }
1866
+ };
619
1867
  var GIT_READONLY_SUBCOMMANDS = [
620
1868
  "log",
621
1869
  "status",
622
1870
  "diff",
623
1871
  "show",
624
1872
  "ls-files",
625
- "ls-remote",
626
1873
  "ls-tree",
627
1874
  "branch",
628
1875
  "tag",
@@ -737,6 +1984,26 @@ var REFUSED_SUBCOMMANDS = {
737
1984
  az: ["login", "logout"],
738
1985
  terraform: ["apply", "destroy"]
739
1986
  };
1987
+ var SHELL_KEYWORDS = [
1988
+ "{",
1989
+ "}",
1990
+ "if",
1991
+ "then",
1992
+ "elif",
1993
+ "else",
1994
+ "fi",
1995
+ "for",
1996
+ "while",
1997
+ "until",
1998
+ "do",
1999
+ "done",
2000
+ "case",
2001
+ "esac",
2002
+ "select",
2003
+ "function",
2004
+ "time",
2005
+ "export"
2006
+ ];
740
2007
  var INTERPRETERS = [
741
2008
  "sh",
742
2009
  "bash",
@@ -806,6 +2073,52 @@ var MULTIPLEXERS = [
806
2073
  "flutter",
807
2074
  "dart"
808
2075
  ];
2076
+ var HELP_FLAGS = ["--help", "--version"];
2077
+ var WRAPPER_FAMILY = {
2078
+ env: {
2079
+ valueFlags: ["-u", "--unset", "-C", "--chdir"],
2080
+ booleanFlags: [
2081
+ "-i",
2082
+ "--ignore-environment",
2083
+ "-0",
2084
+ "--null",
2085
+ "-v",
2086
+ "--debug",
2087
+ // Optional-value flags: see WrapperSpec.booleanFlags.
2088
+ "--block-signal",
2089
+ "--default-signal",
2090
+ "--ignore-signal",
2091
+ "--list-signal-handling",
2092
+ ...HELP_FLAGS
2093
+ ],
2094
+ stringFlags: ["-S", "--split-string"],
2095
+ acceptsAssignments: true
2096
+ },
2097
+ nice: {
2098
+ valueFlags: ["-n", "--adjustment"],
2099
+ booleanFlags: HELP_FLAGS,
2100
+ booleanPattern: /^-\d+$/
2101
+ },
2102
+ timeout: {
2103
+ valueFlags: ["-s", "--signal", "-k", "--kill-after"],
2104
+ booleanFlags: ["--preserve-status", "--foreground", "-v", "--verbose", ...HELP_FLAGS],
2105
+ positionals: 1
2106
+ },
2107
+ nohup: { booleanFlags: HELP_FLAGS },
2108
+ setsid: { booleanFlags: ["-c", "--ctty", "-f", "--fork", "-w", "--wait", "-h", "-V", ...HELP_FLAGS] },
2109
+ stdbuf: {
2110
+ valueFlags: ["-i", "--input", "-o", "--output", "-e", "--error"],
2111
+ booleanFlags: HELP_FLAGS
2112
+ },
2113
+ ionice: {
2114
+ valueFlags: ["-c", "--class", "-n", "--classdata", "-p", "--pid", "-P", "--pgid", "-u", "--uid"],
2115
+ booleanFlags: ["-t", "--ignore", "-h", "-V", ...HELP_FLAGS]
2116
+ },
2117
+ // The multi-call binary ships its own `rm`, `mv` and `sh`, so the refused
2118
+ // name arrives as its first argument.
2119
+ busybox: { booleanFlags: ["--list", "--install", ...HELP_FLAGS] },
2120
+ command: { booleanFlags: ["-p"], noExecFlags: ["-v", "-V"] }
2121
+ };
809
2122
  var POSIX_DIALECT = {
810
2123
  escape: "\\",
811
2124
  escapeInQuotes: true,
@@ -1020,10 +2333,92 @@ function binaryName(token, dialect) {
1020
2333
  if (dot <= 0) return base;
1021
2334
  return dialect.strippedExtensions.includes(base.slice(dot).toLowerCase()) ? base.slice(0, dot) : base;
1022
2335
  }
2336
+ var ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=/;
1023
2337
  function toSegment(tokens, piped, dialect) {
1024
2338
  const rest = [...tokens];
1025
- while (rest.length > 0 && /^[A-Za-z_][A-Za-z0-9_]*=/.test(rest[0])) rest.shift();
1026
- return { binary: rest.length > 0 ? binaryName(rest[0], dialect) : "", args: rest.slice(1), piped, expandable: false };
2339
+ const assignments = [];
2340
+ while (rest.length > 0 && ASSIGNMENT.test(rest[0])) assignments.push(rest.shift());
2341
+ return {
2342
+ binary: rest.length > 0 ? binaryName(rest[0], dialect) : "",
2343
+ args: rest.slice(1),
2344
+ assignments,
2345
+ piped,
2346
+ expandable: false
2347
+ };
2348
+ }
2349
+ function joinedShortFlag(list, token) {
2350
+ if (token.length <= 2) return void 0;
2351
+ return list.find((f) => f.length === 2 && token.startsWith(f));
2352
+ }
2353
+ function scanWrapperArgs(binary, spec, args) {
2354
+ const valueFlags = spec.valueFlags ?? [];
2355
+ const stringFlags = spec.stringFlags ?? [];
2356
+ const assignments = [];
2357
+ let positionals = spec.positionals ?? 0;
2358
+ let i = 0;
2359
+ while (i < args.length) {
2360
+ const token = args[i];
2361
+ if (token === "--") {
2362
+ i++;
2363
+ break;
2364
+ }
2365
+ if (/^-./.test(token)) {
2366
+ const eq = token.startsWith("--") ? token.indexOf("=") : -1;
2367
+ const name = eq > 0 ? token.slice(0, eq) : token;
2368
+ const selfContained = eq > 0;
2369
+ if ((spec.noExecFlags ?? []).includes(name)) return { kind: "no-exec" };
2370
+ if (stringFlags.includes(name) || joinedShortFlag(stringFlags, token)) {
2371
+ return {
2372
+ kind: "refuse",
2373
+ reason: `"${binary} ${name}" splits a string into a command, which this classifier cannot inspect. Use the read-only research tools, or describe it as a task.`
2374
+ };
2375
+ }
2376
+ if ((spec.booleanFlags ?? []).includes(name) || spec.booleanPattern?.test(token)) {
2377
+ i++;
2378
+ continue;
2379
+ }
2380
+ if (valueFlags.includes(name)) {
2381
+ i += selfContained ? 1 : 2;
2382
+ continue;
2383
+ }
2384
+ if (joinedShortFlag(valueFlags, token)) {
2385
+ i++;
2386
+ continue;
2387
+ }
2388
+ return {
2389
+ kind: "refuse",
2390
+ reason: `"${token}" is not a flag this classifier knows on the wrapper "${binary}", so it cannot tell which command "${binary}" would actually run. Re-run without it.`
2391
+ };
2392
+ }
2393
+ if (spec.acceptsAssignments && ASSIGNMENT.test(token)) {
2394
+ assignments.push(token);
2395
+ i++;
2396
+ continue;
2397
+ }
2398
+ if (positionals > 0) {
2399
+ positionals--;
2400
+ i++;
2401
+ continue;
2402
+ }
2403
+ break;
2404
+ }
2405
+ return { kind: "command", tokens: args.slice(i), assignments };
2406
+ }
2407
+ function unwrap(seg, dialect) {
2408
+ const wrappers = [];
2409
+ const assignments = [...seg.assignments];
2410
+ let current = seg;
2411
+ while (WRAPPER_FAMILY[current.binary]) {
2412
+ const scan = scanWrapperArgs(current.binary, WRAPPER_FAMILY[current.binary], current.args);
2413
+ if (scan.kind === "refuse") return { seg: { ...current, assignments }, wrappers, reason: scan.reason };
2414
+ if (scan.kind === "no-exec" || scan.tokens.length === 0) break;
2415
+ assignments.push(...scan.assignments);
2416
+ wrappers.push(current.binary);
2417
+ const inner = toSegment(scan.tokens, current.piped, dialect);
2418
+ assignments.push(...inner.assignments);
2419
+ current = { ...inner, assignments: [], expandable: current.expandable };
2420
+ }
2421
+ return { seg: { ...current, assignments }, wrappers };
1027
2422
  }
1028
2423
  function lexAll(command, dialect) {
1029
2424
  const queue = [];
@@ -1052,16 +2447,79 @@ function pathLikeArgs(command, opts = {}) {
1052
2447
  return !a.startsWith("-") && looksLikePath(a) ? [a] : [];
1053
2448
  }));
1054
2449
  }
2450
+ function matchFlag(spec, booleans, values, token) {
2451
+ if (token.startsWith("--")) {
2452
+ const eq = token.indexOf("=");
2453
+ const name = eq > 0 ? token.slice(0, eq) : token;
2454
+ if (booleans.has(name)) return "boolean";
2455
+ if (values.has(name)) return eq > 0 ? "boolean" : "value";
2456
+ return void 0;
2457
+ }
2458
+ if (booleans.has(token)) return "boolean";
2459
+ if (values.has(token)) return "value";
2460
+ if (spec.patterns?.some((p) => p.test(token))) return "boolean";
2461
+ if (!spec.cluster) return void 0;
2462
+ for (let i = 1; i < token.length; i++) {
2463
+ const short = `-${token[i]}`;
2464
+ if (booleans.has(short)) continue;
2465
+ if (values.has(short)) return i === token.length - 1 ? "value" : "boolean";
2466
+ return void 0;
2467
+ }
2468
+ return "boolean";
2469
+ }
2470
+ function takesNextToken(next) {
2471
+ if (next === void 0) return false;
2472
+ return !/^-./.test(next) || /^-\d/.test(next);
2473
+ }
2474
+ function flagLabel(token) {
2475
+ const eq = token.startsWith("--") ? token.indexOf("=") : -1;
2476
+ return eq > 0 ? token.slice(0, eq) : token;
2477
+ }
2478
+ function scanFlags(seg) {
2479
+ const spec = FLAG_POLICY[seg.binary];
2480
+ if (!spec || spec.freeform) return void 0;
2481
+ const booleans = /* @__PURE__ */ new Set([...spec.booleans ?? [], ...UNIVERSAL_FLAGS]);
2482
+ const values = new Set(spec.values ?? []);
2483
+ const operands = [];
2484
+ let i = 0;
2485
+ while (i < seg.args.length) {
2486
+ const token = seg.args[i];
2487
+ if (token === "--" && !spec.expressionArgs) {
2488
+ operands.push(...seg.args.slice(i + 1));
2489
+ break;
2490
+ }
2491
+ if (!/^-./.test(token)) {
2492
+ operands.push(token);
2493
+ i++;
2494
+ continue;
2495
+ }
2496
+ const shape = matchFlag(spec, booleans, values, token);
2497
+ if (shape === void 0) return { unknown: flagLabel(token), operands };
2498
+ i += shape === "value" && takesNextToken(seg.args[i + 1]) ? 2 : 1;
2499
+ }
2500
+ return { operands };
2501
+ }
2502
+ function subcommandOf(seg) {
2503
+ const scan = scanFlags(seg);
2504
+ if (scan) return scan.operands[0];
2505
+ return seg.args.find((a) => !a.startsWith("-"));
2506
+ }
2507
+ var SCOPE_LEAD_ARGS = 2;
1055
2508
  function scopeFor(seg) {
1056
2509
  if (!MULTIPLEXERS.includes(seg.binary)) return seg.binary;
1057
- const sub = seg.args.find((a) => !a.startsWith("-"));
1058
- return sub ? `${seg.binary} ${sub}` : seg.binary;
2510
+ const lead = [];
2511
+ for (const arg of seg.args) {
2512
+ if (arg.startsWith("-")) break;
2513
+ lead.push(arg);
2514
+ if (lead.length === SCOPE_LEAD_ARGS) break;
2515
+ }
2516
+ return [seg.binary, ...lead].join(" ");
1059
2517
  }
1060
2518
  function isAuto(seg) {
1061
2519
  if (!AUTO_COMMANDS.includes(seg.binary)) return false;
1062
2520
  if (seg.expandable) return false;
1063
2521
  if (seg.binary !== "git") return true;
1064
- const sub = seg.args.find((a) => !a.startsWith("-"));
2522
+ const sub = subcommandOf(seg);
1065
2523
  return sub !== void 0 && GIT_READONLY_SUBCOMMANDS.includes(sub);
1066
2524
  }
1067
2525
  function refusalFor(seg) {
@@ -1071,20 +2529,32 @@ function refusalFor(seg) {
1071
2529
  if (seg.binary === "eval" || seg.binary === "exec") {
1072
2530
  return `"${seg.binary}" runs a string as a command, which this classifier cannot inspect. Use the read-only research tools, or describe it as a task.`;
1073
2531
  }
2532
+ if (seg.binary === "source" || seg.binary === ".") {
2533
+ return `"${seg.binary}" runs a file's contents as commands, which this classifier cannot inspect. Use the read-only research tools, or describe it as a task.`;
2534
+ }
2535
+ if (SHELL_KEYWORDS.includes(seg.binary)) {
2536
+ return `"${seg.binary}" is a shell keyword or compound-command opener. This classifier only inspects the command that runs first, and a keyword hides the real one from it. Run the inner command directly, or describe it as a task.`;
2537
+ }
1074
2538
  const subs = REFUSED_SUBCOMMANDS[seg.binary];
1075
2539
  if (subs) {
1076
- const sub = seg.args.find((a) => !a.startsWith("-"));
2540
+ const sub = subcommandOf(seg);
1077
2541
  if (sub && subs.includes(sub)) {
1078
2542
  return `"${seg.binary} ${sub}" changes state or reaches outward. You are a read-only planner \u2014 put it in the plan and let the runner do it.`;
1079
2543
  }
1080
2544
  }
1081
2545
  if (seg.binary === "git") {
1082
- const sub = seg.args.find((a) => !a.startsWith("-"));
2546
+ const sub = subcommandOf(seg);
1083
2547
  if (sub === "branch" || sub === "tag") {
1084
2548
  const flag = seg.args.find((a) => ["-D", "-d", "-m", "-M", "--delete", "--move"].includes(a));
1085
2549
  if (flag) {
1086
2550
  return `"git ${sub} ${flag}" mutates refs. You are a read-only planner \u2014 describe the change as a task instead.`;
1087
2551
  }
2552
+ const listing = seg.args.includes("-l") || seg.args.includes("--list");
2553
+ const scan2 = scanFlags(seg);
2554
+ const target = scan2?.operands[1];
2555
+ if (!listing && target) {
2556
+ return `"git ${sub} ${target}" creates or moves a ref. You are a read-only planner \u2014 describe the change as a task instead.`;
2557
+ }
1088
2558
  }
1089
2559
  }
1090
2560
  if (seg.binary === "find") {
@@ -1108,8 +2578,24 @@ function refusalFor(seg) {
1108
2578
  return `Inline code via "${seg.binary} ${seg.args.find(isInlineCodeFlag)}" is not available to the planner. Use the read-only research tools, or describe it as a task.`;
1109
2579
  }
1110
2580
  }
2581
+ if (seg.assignments.length > 0) {
2582
+ return `The environment assignment "${seg.assignments[0]}" decides what "${seg.binary}" actually does, and this classifier cannot judge the value. Re-run the command without the assignment.`;
2583
+ }
2584
+ const scan = scanFlags(seg);
2585
+ if (scan?.unknown) {
2586
+ return `"${scan.unknown}" is not a flag this classifier knows to be read-only on "${seg.binary}", and flags on otherwise read-only binaries can run a helper program or write a file. Re-run with read-only flags only, or describe the work as a task.`;
2587
+ }
2588
+ if (seg.binary === "uniq" && scan && scan.operands.length > 1) {
2589
+ return `"uniq" writes to its second file argument ("${scan.operands[1]}"). You are a read-only planner \u2014 pipe the output instead of naming an output file, or describe the write as a task.`;
2590
+ }
1111
2591
  return void 0;
1112
2592
  }
2593
+ function withWrapperNote(reason, wrappers) {
2594
+ if (wrappers.length === 0) return reason;
2595
+ const quoted = wrappers.map((w) => `"${w}"`);
2596
+ const names = quoted.length > 1 ? `${quoted.slice(0, -1).join(", ")} and ${quoted[quoted.length - 1]}` : quoted[0];
2597
+ return `${reason} Wrapping it in ${names} does not change what runs.`;
2598
+ }
1113
2599
  function classifyCommand(command, opts = {}) {
1114
2600
  const trimmed = command.trim();
1115
2601
  if (!trimmed) return { tier: "refuse", scope: "", reason: "Empty command." };
@@ -1138,11 +2624,13 @@ function classifyCommand(command, opts = {}) {
1138
2624
  };
1139
2625
  }
1140
2626
  if (segments.length === 0) return { tier: "refuse", scope: "", reason: "No command found." };
1141
- for (const seg of segments) {
2627
+ const unwrapped = segments.map((seg) => unwrap(seg, dialect));
2628
+ for (const { seg, wrappers, reason: wrapperReason } of unwrapped) {
2629
+ if (wrapperReason) return { tier: "refuse", scope: "", reason: wrapperReason };
1142
2630
  const reason = refusalFor(seg);
1143
- if (reason) return { tier: "refuse", scope: "", reason };
2631
+ if (reason) return { tier: "refuse", scope: "", reason: withWrapperNote(reason, wrappers) };
1144
2632
  }
1145
- const nonAuto = segments.filter((s) => !isAuto(s));
2633
+ const nonAuto = unwrapped.map((u) => u.seg).filter((s) => !isAuto(s));
1146
2634
  if (nonAuto.length === 0) return { tier: "auto", scope: "" };
1147
2635
  const scope = [...new Set(nonAuto.map(scopeFor))].sort().join(" + ");
1148
2636
  return { tier: "ask", scope };
@@ -1486,6 +2974,139 @@ It is outside the auto-allowed read-only set (${scope}). Continue with the read-
1486
2974
  }
1487
2975
  };
1488
2976
 
2977
+ // src/utils/redactSecrets.ts
2978
+ var REDACTION_MARKER = "REDACTED";
2979
+ function marker(kind, length) {
2980
+ return `[${REDACTION_MARKER} ${kind}: ${length} chars]`;
2981
+ }
2982
+ var LINE_PREFIX = String.raw`(?:[ \t]*\d+\||[^\s:]*:\d+:|)[ \t]*`;
2983
+ var LINE_PREFIX_ANCHORED = new RegExp("^" + LINE_PREFIX);
2984
+ var CREDENTIAL_WORD = String.raw`(?:api[_-]?keys?|secrets?|passwords?|passwd|passphrase|tokens?|access[_-]?keys?|private[_-]?keys?|client[_-]?secrets?|credentials?|auth)(?![a-z])`;
2985
+ var NAMED_CREDENTIAL = new RegExp(
2986
+ String.raw`((?:^|[^A-Za-z0-9_])["']?[A-Za-z0-9_.\-]*` + CREDENTIAL_WORD + String.raw`[A-Za-z0-9_.\-]*["']?[ \t]*(?:=>|[:=])[ \t]*)(["'\x60]?)([A-Za-z0-9_\-./+=:~!@#%^&*]{8,})\2`,
2987
+ "gi"
2988
+ );
2989
+ var DOTENV_CREDENTIAL = new RegExp(
2990
+ String.raw`^(` + LINE_PREFIX + String.raw`(?:export[ \t]+)?[A-Z0-9_]*` + CREDENTIAL_WORD.toUpperCase() + String.raw`[A-Z0-9_]*[ \t]*=[ \t]*)(["'\x60]?)([^\s"'\x60]{3,})\2[ \t]*$`,
2991
+ "gm"
2992
+ );
2993
+ var PROVIDER_KEY_PATTERNS = [
2994
+ // OpenAI / Anthropic / OpenRouter and the many `sk-` derivatives
2995
+ /(?<![A-Za-z0-9])sk-(?:ant-|proj-|or-v1-|live-|test-)?[A-Za-z0-9_-]{20,}/g,
2996
+ // GitHub personal-access, OAuth, app and refresh tokens
2997
+ /(?<![A-Za-z0-9])(?:gh[pousr]|github_pat)_[A-Za-z0-9_]{22,}/g,
2998
+ // Slack bot / user / app tokens
2999
+ /(?<![A-Za-z0-9])xox[abprse]-[A-Za-z0-9-]{10,}/g,
3000
+ // Stripe secret and restricted keys
3001
+ /(?<![A-Za-z0-9])[sr]k_(?:live|test)_[A-Za-z0-9]{16,}/g,
3002
+ // Google API keys and OAuth access tokens
3003
+ /(?<![A-Za-z0-9])AIza[A-Za-z0-9_-]{30,}/g,
3004
+ /(?<![A-Za-z0-9])ya29\.[A-Za-z0-9_-]{20,}/g,
3005
+ // npm, Hugging Face, Groq, GitLab, DigitalOcean, Shopify
3006
+ /(?<![A-Za-z0-9])(?:npm|hf|gsk|glpat|dop_v1|shpat|shpss)[_-][A-Za-z0-9]{20,}/g,
3007
+ /(?<![A-Za-z0-9])xai-[A-Za-z0-9]{20,}/g,
3008
+ // Three-segment JWTs, whose payload segment is base64 for `{"`
3009
+ /(?<![A-Za-z0-9])eyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g
3010
+ ];
3011
+ var CLOUD_ACCESS_KEY_ID = /(?<![A-Za-z0-9])(?:A3T[A-Z0-9]{2}|AKIA|ASIA|ABIA|ACCA|AGPA|AIDA|AIPA|ANPA|ANVA|AROA)[A-Z0-9]{16}(?![A-Za-z0-9])/g;
3012
+ var PEM_BEGIN = /-----BEGIN ((?:[A-Z0-9]+ )*PRIVATE KEY(?: BLOCK)?)-----/;
3013
+ var PEM_BODY_LINE = /^[A-Za-z0-9+/=:,.\- \t]*$/;
3014
+ function redactPrivateKeyBlocks(text) {
3015
+ if (!PEM_BEGIN.test(text)) return text;
3016
+ const out = [];
3017
+ let label = null;
3018
+ let bodyChars = 0;
3019
+ const closeBlock = () => {
3020
+ if (label === null) return;
3021
+ out.push(marker("private key", bodyChars));
3022
+ label = null;
3023
+ bodyChars = 0;
3024
+ };
3025
+ for (const line of text.split("\n")) {
3026
+ if (label === null) {
3027
+ out.push(line);
3028
+ const begun = PEM_BEGIN.exec(line);
3029
+ if (begun) label = begun[1];
3030
+ continue;
3031
+ }
3032
+ const content = line.replace(LINE_PREFIX_ANCHORED, "");
3033
+ if (content.includes(`-----END ${label}-----`)) {
3034
+ closeBlock();
3035
+ out.push(line);
3036
+ } else if (PEM_BODY_LINE.test(content)) {
3037
+ bodyChars += content.trim().length;
3038
+ } else {
3039
+ closeBlock();
3040
+ out.push(line);
3041
+ }
3042
+ }
3043
+ closeBlock();
3044
+ return out.join("\n");
3045
+ }
3046
+ var NUMERIC = /^[-+]?\d+(?:\.\d+)?$/;
3047
+ var IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
3048
+ var MEMBER_EXPRESSION = /^[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)+$/;
3049
+ var SLUG = /^[a-z]+(?:[-_][a-z]+)+$/;
3050
+ var PATHISH = /^(?:\.{0,2}\/|~\/)/;
3051
+ var URLISH = /^[a-z][a-z0-9+.-]*:\/\//i;
3052
+ var CODE_KEYWORDS = /* @__PURE__ */ new Set([
3053
+ "true",
3054
+ "false",
3055
+ "null",
3056
+ "none",
3057
+ "nil",
3058
+ "undefined",
3059
+ "void",
3060
+ "string",
3061
+ "number",
3062
+ "boolean",
3063
+ "object",
3064
+ "any",
3065
+ "unknown",
3066
+ "bool",
3067
+ "str",
3068
+ "int"
3069
+ ]);
3070
+ var UNAMBIGUOUS_CREDENTIAL_NAME = /(?:password|passwd|passphrase|secret|credentials?|private[_-]?key)/i;
3071
+ function looksLikeKeyMaterial(value) {
3072
+ const hasDigit = /\d/.test(value);
3073
+ const hasAlpha = /[A-Za-z]/.test(value);
3074
+ const mixedCase = /[a-z]/.test(value) && /[A-Z]/.test(value);
3075
+ return hasDigit && hasAlpha || mixedCase || value.length >= 24;
3076
+ }
3077
+ function looksLikeCode(value) {
3078
+ return NUMERIC.test(value) || CODE_KEYWORDS.has(value.toLowerCase()) || MEMBER_EXPRESSION.test(value) || PATHISH.test(value) || URLISH.test(value);
3079
+ }
3080
+ function trimTrailingPunctuation(value) {
3081
+ const match = /[.:!]+$/.exec(value);
3082
+ if (!match || match.index === 0) return { value, tail: "" };
3083
+ return { value: value.slice(0, match.index), tail: match[0] };
3084
+ }
3085
+ function redactSecrets(text) {
3086
+ if (!text) return text;
3087
+ let out = redactPrivateKeyBlocks(text);
3088
+ out = out.replace(DOTENV_CREDENTIAL, (full, head, quote, value) => {
3089
+ if (looksLikeCode(value) || value.startsWith("$")) return full;
3090
+ return `${head}${quote}${marker("credential", value.length)}${quote}`;
3091
+ });
3092
+ out = out.replace(NAMED_CREDENTIAL, (full, head, quote, raw) => {
3093
+ const { value, tail } = trimTrailingPunctuation(raw);
3094
+ if (value.length < 8) return full;
3095
+ if (looksLikeCode(value) || value.startsWith("$")) return full;
3096
+ if (UNAMBIGUOUS_CREDENTIAL_NAME.test(head) && /\d/.test(value)) {
3097
+ return `${head}${quote}${marker("credential", value.length)}${quote}${tail}`;
3098
+ }
3099
+ if (IDENTIFIER.test(value) || SLUG.test(value)) return full;
3100
+ if (!UNAMBIGUOUS_CREDENTIAL_NAME.test(head) && !looksLikeKeyMaterial(value)) return full;
3101
+ return `${head}${quote}${marker("credential", value.length)}${quote}${tail}`;
3102
+ });
3103
+ for (const pattern of PROVIDER_KEY_PATTERNS) {
3104
+ out = out.replace(pattern, (hit) => marker("api key", hit.length));
3105
+ }
3106
+ out = out.replace(CLOUD_ACCESS_KEY_ID, (hit) => marker("access key id", hit.length));
3107
+ return out;
3108
+ }
3109
+
1489
3110
  // src/services/executeTool.ts
1490
3111
  var GREP_OUTPUT_MODES = ["content", "files", "count"];
1491
3112
  function num(value) {
@@ -1512,6 +3133,10 @@ function grepOptions(args) {
1512
3133
  }
1513
3134
  var STOPPED_TOOL_RESULT = "Stopped by the user \u2014 this call was not executed, and no further tool calls will run this turn.";
1514
3135
  async function executeTool(name, args, fs10, fetcher, signal) {
3136
+ const outcome = await runTool(name, args, fs10, fetcher, signal);
3137
+ return { ...outcome, output: redactSecrets(outcome.output) };
3138
+ }
3139
+ async function runTool(name, args, fs10, fetcher, signal) {
1515
3140
  if (signal?.aborted) return { success: false, output: STOPPED_TOOL_RESULT, truncated: false };
1516
3141
  switch (name) {
1517
3142
  case "read_file": {
@@ -1641,14 +3266,14 @@ var PendingApprovals = class {
1641
3266
  ask(request) {
1642
3267
  const id = `ap-${Date.now()}-${counter++}`;
1643
3268
  const pending = { id, request, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
1644
- return new Promise((resolve3) => {
3269
+ return new Promise((resolve4) => {
1645
3270
  const settle = (granted) => {
1646
3271
  const entry = this.entries.get(id);
1647
3272
  if (!entry) return;
1648
3273
  clearTimeout(entry.timer);
1649
3274
  this.entries.delete(id);
1650
3275
  this.opts.onSettled?.(id, granted);
1651
- resolve3(granted);
3276
+ resolve4(granted);
1652
3277
  };
1653
3278
  const timer = setTimeout(() => settle(false), this.opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);
1654
3279
  timer.unref?.();
@@ -4558,9 +6183,9 @@ function summarizeToolCall(tool, argsJson, toolLabel) {
4558
6183
  const scope = args.include ? ` in ${args.include}` : "";
4559
6184
  return `grep ${ellipsize(String(args.pattern), 40)}${scope}${mode}`;
4560
6185
  }
4561
- const path15 = args.filePath || args.file_path || args.path;
4562
- if (path15) {
4563
- const basename = String(path15).split("/").pop() || String(path15);
6186
+ const path17 = args.filePath || args.file_path || args.path;
6187
+ if (path17) {
6188
+ const basename = String(path17).split("/").pop() || String(path17);
4564
6189
  return `${display} ${basename}`;
4565
6190
  }
4566
6191
  if (args.pattern) {
@@ -5780,11 +7405,11 @@ function composeAugmentedPrompt(task, allTasks, opts) {
5780
7405
  if (isHitlTask(task)) {
5781
7406
  blocks.push(renderCheckpointInstruction());
5782
7407
  }
5783
- const marker = renderCompletionMarker(task);
5784
- if (blocks.length === 0) return basePrompt + marker;
7408
+ const marker2 = renderCompletionMarker(task);
7409
+ if (blocks.length === 0) return basePrompt + marker2;
5785
7410
  return `${blocks.join("\n\n")}
5786
7411
 
5787
- ${basePrompt}${marker}`;
7412
+ ${basePrompt}${marker2}`;
5788
7413
  }
5789
7414
 
5790
7415
  // src/services/VerdictEngine.ts
@@ -7221,28 +8846,28 @@ function posixBinDirs() {
7221
8846
  ];
7222
8847
  }
7223
8848
  function windowsBinDirs(env, home) {
7224
- const join14 = (...parts) => path5.win32.join(...parts);
7225
- const under = (base, ...parts) => base ? [join14(base, ...parts)] : [];
8849
+ const join16 = (...parts) => path5.win32.join(...parts);
8850
+ const under = (base, ...parts) => base ? [join16(base, ...parts)] : [];
7226
8851
  const appData = env.APPDATA;
7227
8852
  const localAppData = env.LOCALAPPDATA;
7228
8853
  const programData = env.ProgramData;
7229
8854
  return [
7230
8855
  // Native per-user installers, PowerShell-driven and otherwise.
7231
- join14(home, ".local", "bin"),
7232
- join14(home, ".opencode", "bin"),
7233
- join14(home, ".claude", "bin"),
7234
- join14(home, ".codex", "bin"),
8856
+ join16(home, ".local", "bin"),
8857
+ join16(home, ".opencode", "bin"),
8858
+ join16(home, ".claude", "bin"),
8859
+ join16(home, ".codex", "bin"),
7235
8860
  ...under(localAppData, "Programs", "claude"),
7236
8861
  // Node package managers. npm's global prefix is `%APPDATA%\npm`; the
7237
8862
  // literal spelling is kept as a fallback for a host that does not export
7238
8863
  // APPDATA (a service account, a stripped CI container).
7239
8864
  ...under(appData, "npm"),
7240
- join14(home, "AppData", "Roaming", "npm"),
8865
+ join16(home, "AppData", "Roaming", "npm"),
7241
8866
  ...under(localAppData, "pnpm"),
7242
8867
  ...under(localAppData, "Yarn", "bin"),
7243
- join14(home, ".bun", "bin"),
8868
+ join16(home, ".bun", "bin"),
7244
8869
  // Windows package managers.
7245
- join14(home, "scoop", "shims"),
8870
+ join16(home, "scoop", "shims"),
7246
8871
  ...under(env.SCOOP, "shims"),
7247
8872
  ...under(env.SCOOP_GLOBAL ?? programData, "scoop", "shims"),
7248
8873
  ...under(programData, "chocolatey", "bin"),
@@ -7252,7 +8877,7 @@ function windowsBinDirs(env, home) {
7252
8877
  ...under(localAppData, "Microsoft", "WindowsApps"),
7253
8878
  // Version managers. Volta's Windows home is not the POSIX `~/.volta`.
7254
8879
  ...under(localAppData, "Volta", "bin"),
7255
- join14(home, ".volta", "bin")
8880
+ join16(home, ".volta", "bin")
7256
8881
  ];
7257
8882
  }
7258
8883
  function wellKnownBinDirs(deps = {}) {
@@ -7553,7 +9178,7 @@ var defaultAppServerClient = async (command, args, options) => {
7553
9178
  const PATH = await augmentedPath();
7554
9179
  const launch = await planDirectLaunch(command, args).catch(() => null);
7555
9180
  if (!launch) return null;
7556
- return new Promise((resolve3) => {
9181
+ return new Promise((resolve4) => {
7557
9182
  let child;
7558
9183
  try {
7559
9184
  child = (0, import_child_process3.spawn)(launch.file, launch.args, {
@@ -7562,7 +9187,7 @@ var defaultAppServerClient = async (command, args, options) => {
7562
9187
  windowsVerbatimArguments: launch.verbatim
7563
9188
  });
7564
9189
  } catch {
7565
- resolve3(null);
9190
+ resolve4(null);
7566
9191
  return;
7567
9192
  }
7568
9193
  let settled = false;
@@ -7571,7 +9196,7 @@ var defaultAppServerClient = async (command, args, options) => {
7571
9196
  settled = true;
7572
9197
  clearTimeout(timer);
7573
9198
  killTree(child);
7574
- resolve3(value);
9199
+ resolve4(value);
7575
9200
  };
7576
9201
  const timer = setTimeout(() => finish(null), options?.timeout ?? 2e4);
7577
9202
  child.on("error", () => finish(null));
@@ -8611,11 +10236,71 @@ var RunnerInstallation = class {
8611
10236
  }
8612
10237
  };
8613
10238
 
10239
+ // src/services/settingsEnvAllowlist.ts
10240
+ var ORDEWELL_SETTABLE_ENV = [
10241
+ "AI_PROVIDER",
10242
+ "ORCHESTRATOR_MODEL",
10243
+ "ORDEWELL_SUBAGENT_MODEL",
10244
+ "ORDEWELL_PLANNER_EFFORT",
10245
+ "ORDEWELL_RESEARCH_ENABLED",
10246
+ "ORDEWELL_MAX_PARALLEL"
10247
+ ];
10248
+ var SETTINGS_ENV_REFUSED = [
10249
+ // Approval posture and pre-approved scope.
10250
+ "ORDEWELL_AUTONOMOUS_MODE",
10251
+ "ORDEWELL_APPROVAL_MODE",
10252
+ "ORDEWELL_APPROVAL_ALLOW",
10253
+ // Where settings are persisted.
10254
+ "ORDEWELL_SETTINGS_PATH",
10255
+ // Runtime options and dynamic loaders.
10256
+ "NODE_OPTIONS",
10257
+ "NODE_REPL_EXTERNAL_MODULE",
10258
+ "NODE_PATH",
10259
+ "ELECTRON_RUN_AS_NODE",
10260
+ "LD_PRELOAD",
10261
+ "LD_LIBRARY_PATH",
10262
+ "LD_AUDIT",
10263
+ "DYLD_INSERT_LIBRARIES",
10264
+ "DYLD_LIBRARY_PATH",
10265
+ "DYLD_FRAMEWORK_PATH",
10266
+ // Executable resolution.
10267
+ "PATH",
10268
+ "PATHEXT",
10269
+ "NODE"
10270
+ ];
10271
+ var REFUSED = new Set(SETTINGS_ENV_REFUSED);
10272
+ function providerEnvKeys() {
10273
+ const keys = [];
10274
+ for (const meta of Object.values(ALL_PROVIDERS)) {
10275
+ if (meta.apiKeyEnvVar) keys.push(meta.apiKeyEnvVar);
10276
+ if (meta.baseUrlEnvVar) keys.push(meta.baseUrlEnvVar);
10277
+ for (const detect of meta.detectEnvVars) keys.push(detect);
10278
+ }
10279
+ return keys;
10280
+ }
10281
+ var PROVIDER_CREDENTIAL_ENV = new Set(providerEnvKeys());
10282
+ var SETTINGS_ENV_ALLOWLIST = new Set(
10283
+ [...providerEnvKeys(), ...ORDEWELL_SETTABLE_ENV].filter((key) => !REFUSED.has(key))
10284
+ );
10285
+ function admitSettingsEnv(env) {
10286
+ const accepted = {};
10287
+ const rejected = [];
10288
+ for (const [key, value] of Object.entries(env)) {
10289
+ if (REFUSED.has(key) || !SETTINGS_ENV_ALLOWLIST.has(key) || typeof value !== "string") {
10290
+ rejected.push(key);
10291
+ continue;
10292
+ }
10293
+ accepted[key] = value;
10294
+ }
10295
+ return { accepted, rejected };
10296
+ }
10297
+
8614
10298
  // src/services/harness/CliAgentAiService.ts
8615
10299
  var import_child_process5 = require("child_process");
8616
10300
 
8617
10301
  // src/utils/workspace.ts
8618
10302
  var fs3 = __toESM(require("fs"));
10303
+ var path9 = __toESM(require("path"));
8619
10304
  var WorkspaceNotFoundError = class extends Error {
8620
10305
  constructor(workspace) {
8621
10306
  super(`Workspace "${workspace}" does not exist.`);
@@ -8635,6 +10320,46 @@ function assertWorkspaceExists(workspace, deps = {}) {
8635
10320
  const isDirectory = deps.isDirectory ?? defaultIsDirectory;
8636
10321
  if (!isDirectory(workspace)) throw new WorkspaceNotFoundError(workspace);
8637
10322
  }
10323
+ var WorkspaceNotAProjectError = class extends Error {
10324
+ constructor(workspace) {
10325
+ super(`Workspace "${workspace}" is not a project directory (no .ordewell state, version control, or recognized manifest found).`);
10326
+ this.workspace = workspace;
10327
+ this.name = "WorkspaceNotAProjectError";
10328
+ }
10329
+ workspace;
10330
+ };
10331
+ var PROJECT_MARKER_DIRS = [".ordewell", ".git", ".hg", ".svn", ".bzr"];
10332
+ var PROJECT_MARKER_FILES = [
10333
+ "package.json",
10334
+ "Cargo.toml",
10335
+ "go.mod",
10336
+ "pyproject.toml",
10337
+ "setup.py",
10338
+ "requirements.txt",
10339
+ "Pipfile",
10340
+ "pom.xml",
10341
+ "build.gradle",
10342
+ "build.gradle.kts",
10343
+ "Gemfile",
10344
+ "composer.json",
10345
+ "pubspec.yaml",
10346
+ "mix.exs",
10347
+ "CMakeLists.txt"
10348
+ ];
10349
+ function assertWorkspaceIsProject(workspace, deps = {}) {
10350
+ const realpath = deps.realpath ?? ((candidate) => fs3.realpathSync(candidate));
10351
+ const exists = deps.exists ?? ((candidate) => fs3.existsSync(candidate));
10352
+ let resolved;
10353
+ try {
10354
+ resolved = realpath(workspace);
10355
+ } catch {
10356
+ throw new WorkspaceNotAProjectError(workspace);
10357
+ }
10358
+ const hasMarker = [...PROJECT_MARKER_DIRS, ...PROJECT_MARKER_FILES].some(
10359
+ (marker2) => exists(path9.join(resolved, marker2))
10360
+ );
10361
+ if (!hasMarker) throw new WorkspaceNotAProjectError(workspace);
10362
+ }
8638
10363
 
8639
10364
  // src/services/harness/AgentAdapter.ts
8640
10365
  var LineBuffer = class {
@@ -8690,8 +10415,8 @@ var StdioAgentAdapter = class {
8690
10415
  }
8691
10416
  async start(opts) {
8692
10417
  assertWorkspaceExists(opts.cwd, { isDirectory: this.deps.isDirectory });
8693
- this.processEnded = new Promise((resolve3) => {
8694
- this.markEnded = resolve3;
10418
+ this.processEnded = new Promise((resolve4) => {
10419
+ this.markEnded = resolve4;
8695
10420
  });
8696
10421
  const spec = this.spawnSpec(opts);
8697
10422
  const resolvePath = this.deps.resolvePath ?? augmentedPath;
@@ -8739,7 +10464,7 @@ ${err.message}`).slice(-STDERR_TAIL_CHARS);
8739
10464
  onEvent({ type: "error", message: this.exitMessage() });
8740
10465
  return;
8741
10466
  }
8742
- await new Promise((resolve3) => {
10467
+ await new Promise((resolve4) => {
8743
10468
  let settled = false;
8744
10469
  const finish = () => {
8745
10470
  if (settled) return;
@@ -8748,7 +10473,7 @@ ${err.message}`).slice(-STDERR_TAIL_CHARS);
8748
10473
  signal?.removeEventListener("abort", onAbort);
8749
10474
  this.process?.removeListener("exit", onExit);
8750
10475
  this.process?.removeListener("error", onExit);
8751
- resolve3();
10476
+ resolve4();
8752
10477
  };
8753
10478
  const emit = (event) => {
8754
10479
  onEvent(event);
@@ -8949,14 +10674,14 @@ function codexSandboxUnavailableMessage() {
8949
10674
  ].join("\n");
8950
10675
  }
8951
10676
  function runProbe(deps, cwd, env, flags) {
8952
- return new Promise((resolve3) => {
10677
+ return new Promise((resolve4) => {
8953
10678
  let output = "";
8954
10679
  let settled = false;
8955
10680
  const finish = (code) => {
8956
10681
  if (settled) return;
8957
10682
  settled = true;
8958
10683
  clearTimeout(timer);
8959
- resolve3({ code, output });
10684
+ resolve4({ code, output });
8960
10685
  };
8961
10686
  const timer = setTimeout(() => finish(null), PROBE_TIMEOUT_MS);
8962
10687
  timer.unref?.();
@@ -9039,8 +10764,8 @@ var CodexAdapter = class extends StdioAgentAdapter {
9039
10764
  async handshake(opts) {
9040
10765
  this.sandbox = await probeCodexSandbox(this.deps, opts.cwd, this.spawnEnv);
9041
10766
  if (this.sandbox === "unavailable") throw new Error(codexSandboxUnavailableMessage());
9042
- const ready = new Promise((resolve3) => {
9043
- this.settleHandshake = resolve3;
10767
+ const ready = new Promise((resolve4) => {
10768
+ this.settleHandshake = resolve4;
9044
10769
  });
9045
10770
  this.writeLine({
9046
10771
  jsonrpc: "2.0",
@@ -9053,8 +10778,8 @@ var CodexAdapter = class extends StdioAgentAdapter {
9053
10778
  // A binary that rejects `app-server` outright dies immediately; waiting
9054
10779
  // out the timeout would turn a one-line diagnostic into a 30s stall.
9055
10780
  this.processEnded.then(() => false),
9056
- new Promise((resolve3) => {
9057
- const t = setTimeout(() => resolve3(false), HANDSHAKE_TIMEOUT_MS);
10781
+ new Promise((resolve4) => {
10782
+ const t = setTimeout(() => resolve4(false), HANDSHAKE_TIMEOUT_MS);
9058
10783
  t.unref?.();
9059
10784
  })
9060
10785
  ]);
@@ -9342,12 +11067,12 @@ var OpenCodeAdapter = class {
9342
11067
  cwd: opts.cwd,
9343
11068
  windowsVerbatimArguments: launch.verbatim
9344
11069
  });
9345
- const banner = new Promise((resolve3) => {
11070
+ const banner = new Promise((resolve4) => {
9346
11071
  let seen = "";
9347
11072
  const scan = (chunk) => {
9348
11073
  seen += chunk.toString();
9349
11074
  const match = seen.match(/https?:\/\/[^\s]+/);
9350
- if (match) resolve3(match[0].replace(/[.,)]$/, ""));
11075
+ if (match) resolve4(match[0].replace(/[.,)]$/, ""));
9351
11076
  };
9352
11077
  this.process.stdout?.on("data", scan);
9353
11078
  this.process.stderr?.on("data", (chunk) => {
@@ -9356,15 +11081,15 @@ var OpenCodeAdapter = class {
9356
11081
  });
9357
11082
  this.process.on("exit", () => {
9358
11083
  this.exited = true;
9359
- resolve3(null);
11084
+ resolve4(null);
9360
11085
  });
9361
11086
  this.process.on("error", (err) => {
9362
11087
  this.exited = true;
9363
11088
  this.stderrTail = (this.stderrTail + `
9364
11089
  ${err.message}`).slice(-STDERR_TAIL_CHARS2);
9365
- resolve3(null);
11090
+ resolve4(null);
9366
11091
  });
9367
- const timer = setTimeout(() => resolve3(null), SERVER_READY_TIMEOUT_MS);
11092
+ const timer = setTimeout(() => resolve4(null), SERVER_READY_TIMEOUT_MS);
9368
11093
  timer.unref?.();
9369
11094
  });
9370
11095
  this.baseUrl = await banner;
@@ -9394,8 +11119,8 @@ ${this.stderrTail.trim()}` : ""}`);
9394
11119
  const streamAbort = new AbortController();
9395
11120
  let connected = () => {
9396
11121
  };
9397
- const streamReady = new Promise((resolve3) => {
9398
- connected = resolve3;
11122
+ const streamReady = new Promise((resolve4) => {
11123
+ connected = resolve4;
9399
11124
  });
9400
11125
  const live = this.streamEvents(
9401
11126
  streamAbort.signal,
@@ -9547,14 +11272,14 @@ ${this.stderrTail.trim()}` : ""}`);
9547
11272
  }
9548
11273
  }
9549
11274
  }
9550
- async json(method, path15, body, signal) {
9551
- const response = await this.deps.fetch(`${this.baseUrl}${path15}`, {
11275
+ async json(method, path17, body, signal) {
11276
+ const response = await this.deps.fetch(`${this.baseUrl}${path17}`, {
9552
11277
  method,
9553
11278
  headers: { "content-type": "application/json" },
9554
11279
  ...body === void 0 ? {} : { body: JSON.stringify(body) },
9555
11280
  ...signal ? { signal } : {}
9556
11281
  });
9557
- if (!response.ok) throw new Error(`${method} ${path15} failed: ${response.status} ${response.statusText}`);
11282
+ if (!response.ok) throw new Error(`${method} ${path17} failed: ${response.status} ${response.statusText}`);
9558
11283
  return await response.json().catch(() => null);
9559
11284
  }
9560
11285
  nativeSessionId() {
@@ -9851,7 +11576,8 @@ var CliAgentAiService = class {
9851
11576
  let stepIndex = 0;
9852
11577
  const truncate = (value) => value.length > LOG_MAX_CHARS ? `${value.slice(0, LOG_MAX_CHARS)}
9853
11578
  [... truncated, total ${value.length} chars]` : value;
9854
- const settle = (id, output, success, outcome) => {
11579
+ const settle = (id, rawOutput, success, outcome) => {
11580
+ const output = redactSecrets(rawOutput);
9855
11581
  const call = pendingCalls.get(id);
9856
11582
  pendingCalls.delete(id);
9857
11583
  const step = {
@@ -10368,11 +12094,11 @@ function executionSummary(tasks) {
10368
12094
 
10369
12095
  // src/utils/sessionStore.ts
10370
12096
  var fs5 = __toESM(require("fs"));
10371
- var path10 = __toESM(require("path"));
12097
+ var path11 = __toESM(require("path"));
10372
12098
 
10373
12099
  // src/utils/fsHelpers.ts
10374
12100
  var fs4 = __toESM(require("fs"));
10375
- var path9 = __toESM(require("path"));
12101
+ var path10 = __toESM(require("path"));
10376
12102
  var STATE_DIR = ".ordewell";
10377
12103
  function ensureDir(dir) {
10378
12104
  if (!fs4.existsSync(dir)) {
@@ -10380,13 +12106,29 @@ function ensureDir(dir) {
10380
12106
  }
10381
12107
  }
10382
12108
  function getStateDir(baseDir) {
10383
- return path9.join(baseDir ?? process.cwd(), STATE_DIR);
12109
+ return path10.join(baseDir ?? process.cwd(), STATE_DIR);
12110
+ }
12111
+ var IGNORE_FILE = ".gitignore";
12112
+ function ensureStateDirIgnored(baseDir) {
12113
+ const dir = getStateDir(baseDir);
12114
+ ensureDir(dir);
12115
+ try {
12116
+ fs4.writeFileSync(path10.join(dir, IGNORE_FILE), "*\n", { flag: "wx" });
12117
+ } catch (err) {
12118
+ if (err.code !== "EEXIST") throw err;
12119
+ }
12120
+ }
12121
+
12122
+ // src/utils/sessionId.ts
12123
+ var import_crypto = require("crypto");
12124
+ function mintSessionId() {
12125
+ return `session-${(0, import_crypto.randomBytes)(16).toString("hex")}`;
10384
12126
  }
10385
12127
 
10386
12128
  // src/utils/sessionStore.ts
10387
12129
  var SESSIONS_SUBDIR = "sessions";
10388
12130
  function getSessionsDir(baseDir) {
10389
- return path10.join(getStateDir(baseDir), SESSIONS_SUBDIR);
12131
+ return path11.join(getStateDir(baseDir), SESSIONS_SUBDIR);
10390
12132
  }
10391
12133
  var REJECTION_MESSAGE = "This session was created with an older version of Ordewell. Please re-plan.";
10392
12134
  function hasScalarRunner(parsed) {
@@ -10405,9 +12147,10 @@ function sessionFilename(session) {
10405
12147
  function saveSession(plan, goal, baseDir, id) {
10406
12148
  const sessionsDir = getSessionsDir(baseDir);
10407
12149
  ensureDir(sessionsDir);
12150
+ ensureStateDirIgnored(baseDir);
10408
12151
  const now = (/* @__PURE__ */ new Date()).toISOString();
10409
12152
  const meta = {
10410
- id: id || `session-${Date.now()}`,
12153
+ id: id || mintSessionId(),
10411
12154
  goal: goal || "(untitled)",
10412
12155
  runners: plan.runners,
10413
12156
  taskCount: plan.tasks.flatMap((t) => [t, ...t.subtasks || []]).length,
@@ -10420,7 +12163,7 @@ function saveSession(plan, goal, baseDir, id) {
10420
12163
  plan: JSON.parse(JSON.stringify(plan))
10421
12164
  };
10422
12165
  const filename = sessionFilename(meta);
10423
- const filePath = path10.join(sessionsDir, filename);
12166
+ const filePath = path11.join(sessionsDir, filename);
10424
12167
  fs5.writeFileSync(filePath, JSON.stringify(session, null, 2));
10425
12168
  return meta;
10426
12169
  }
@@ -10430,7 +12173,7 @@ function listSessions(baseDir, logger = defaultLogger) {
10430
12173
  const files = fs5.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
10431
12174
  return files.map((f) => {
10432
12175
  try {
10433
- const raw = fs5.readFileSync(path10.join(sessionsDir, f), "utf-8");
12176
+ const raw = fs5.readFileSync(path11.join(sessionsDir, f), "utf-8");
10434
12177
  const parsed = JSON.parse(raw);
10435
12178
  if (hasScalarRunner(parsed)) {
10436
12179
  logger.warn("sessionStore", `session file ${f}: ${REJECTION_MESSAGE}`);
@@ -10448,7 +12191,7 @@ function findSessionFile(sessionsDir, sessionId, logger) {
10448
12191
  const files = fs5.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
10449
12192
  for (const f of files) {
10450
12193
  try {
10451
- const raw = fs5.readFileSync(path10.join(sessionsDir, f), "utf-8");
12194
+ const raw = fs5.readFileSync(path11.join(sessionsDir, f), "utf-8");
10452
12195
  const session = JSON.parse(raw);
10453
12196
  if (session.meta.id === sessionId) return f;
10454
12197
  } catch (err) {
@@ -10463,7 +12206,7 @@ function loadSession(sessionId, baseDir, logger = defaultLogger) {
10463
12206
  try {
10464
12207
  const file = findSessionFile(sessionsDir, sessionId, logger);
10465
12208
  if (file) {
10466
- const raw = fs5.readFileSync(path10.join(sessionsDir, file), "utf-8");
12209
+ const raw = fs5.readFileSync(path11.join(sessionsDir, file), "utf-8");
10467
12210
  const parsed = JSON.parse(raw);
10468
12211
  if (hasScalarRunner(parsed)) {
10469
12212
  logger.warn("sessionStore", `session file ${file}: ${REJECTION_MESSAGE}`);
@@ -10497,7 +12240,7 @@ function deleteSession(sessionId, baseDir, logger = defaultLogger) {
10497
12240
  try {
10498
12241
  const file = findSessionFile(sessionsDir, sessionId, logger);
10499
12242
  if (file) {
10500
- fs5.unlinkSync(path10.join(sessionsDir, file));
12243
+ fs5.unlinkSync(path11.join(sessionsDir, file));
10501
12244
  return true;
10502
12245
  }
10503
12246
  } catch (err) {
@@ -10516,11 +12259,6 @@ var PlanEditError = class extends Error {
10516
12259
  function sessionRuntimeSettings(settings) {
10517
12260
  return { ...plannerRuntimeToggles(settings), modelAllowlist: settings.modelAllowlist };
10518
12261
  }
10519
- var lastMintedAt = 0;
10520
- function mintSessionId() {
10521
- lastMintedAt = Math.max(Date.now(), lastMintedAt + 1);
10522
- return `session-${lastMintedAt}`;
10523
- }
10524
12262
  var Session = class {
10525
12263
  /** Injected by a test; when present it is the service, forever. */
10526
12264
  pinnedAiService;
@@ -12318,26 +14056,128 @@ var TmuxRunner = class extends AbstractRunner {
12318
14056
  }
12319
14057
  };
12320
14058
 
14059
+ // src/utils/daemonToken.ts
14060
+ var import_crypto2 = require("crypto");
14061
+ var import_fs2 = require("fs");
14062
+ var import_os2 = require("os");
14063
+ var import_path2 = require("path");
14064
+ var BEARER_PREFIX = "bearer ";
14065
+ var DAEMON_TOKEN_SUBPROTOCOL_PREFIX = "ordewell.token.";
14066
+ var DAEMON_SUBPROTOCOL = "ordewell.v1";
14067
+ function configDir() {
14068
+ return (0, import_path2.join)((0, import_os2.homedir)(), ".config", "ordewell");
14069
+ }
14070
+ function daemonTokenPath(port) {
14071
+ return (0, import_path2.join)(configDir(), `server-${port}.token`);
14072
+ }
14073
+ function mintDaemonToken(port) {
14074
+ const token = (0, import_crypto2.randomBytes)(32).toString("base64url");
14075
+ const file = daemonTokenPath(port);
14076
+ (0, import_fs2.mkdirSync)(configDir(), { recursive: true });
14077
+ try {
14078
+ (0, import_fs2.unlinkSync)(file);
14079
+ } catch {
14080
+ }
14081
+ const fd = (0, import_fs2.openSync)(file, "wx", 384);
14082
+ try {
14083
+ (0, import_fs2.writeSync)(fd, token);
14084
+ } finally {
14085
+ (0, import_fs2.closeSync)(fd);
14086
+ }
14087
+ return { token, file };
14088
+ }
14089
+ function readDaemonToken(port) {
14090
+ try {
14091
+ const token = (0, import_fs2.readFileSync)(daemonTokenPath(port), "utf8").trim();
14092
+ return token === "" ? void 0 : token;
14093
+ } catch {
14094
+ return void 0;
14095
+ }
14096
+ }
14097
+ function clearDaemonToken(port) {
14098
+ try {
14099
+ (0, import_fs2.unlinkSync)(daemonTokenPath(port));
14100
+ } catch {
14101
+ }
14102
+ }
14103
+ function bearerHeaderValue(token) {
14104
+ return `Bearer ${token}`;
14105
+ }
14106
+ function tokenSubprotocols(token) {
14107
+ return [DAEMON_SUBPROTOCOL, `${DAEMON_TOKEN_SUBPROTOCOL_PREFIX}${token}`];
14108
+ }
14109
+ function extractPresentedToken(carriers) {
14110
+ const authorization = carriers.authorization;
14111
+ if (authorization && authorization.toLowerCase().startsWith(BEARER_PREFIX)) {
14112
+ const value = authorization.slice(BEARER_PREFIX.length).trim();
14113
+ if (value !== "") return value;
14114
+ }
14115
+ for (const offered of (carriers.secWebSocketProtocol ?? "").split(",")) {
14116
+ const value = offered.trim();
14117
+ if (!value.startsWith(DAEMON_TOKEN_SUBPROTOCOL_PREFIX)) continue;
14118
+ const token = value.slice(DAEMON_TOKEN_SUBPROTOCOL_PREFIX.length);
14119
+ if (token !== "") return token;
14120
+ }
14121
+ return void 0;
14122
+ }
14123
+ function tokensMatch(presented, expected) {
14124
+ if (presented === void 0) return false;
14125
+ const a = Buffer.from(presented, "utf8");
14126
+ const b = Buffer.from(expected, "utf8");
14127
+ if (a.length !== b.length) return false;
14128
+ return (0, import_crypto2.timingSafeEqual)(a, b);
14129
+ }
14130
+
12321
14131
  // src/plugins/RunnerRegistry.ts
12322
14132
  var fs7 = __toESM(require("fs"));
12323
- var path12 = __toESM(require("path"));
14133
+ var path14 = __toESM(require("path"));
12324
14134
  var os5 = __toESM(require("os"));
12325
14135
  var import_child_process9 = require("child_process");
14136
+ var import_crypto3 = require("crypto");
12326
14137
 
12327
14138
  // src/plugins/FsPluginStore.ts
12328
14139
  var fs6 = __toESM(require("fs"));
12329
- var path11 = __toESM(require("path"));
14140
+ var path13 = __toESM(require("path"));
12330
14141
  var os4 = __toESM(require("os"));
12331
- function userPluginsDir() {
12332
- return path11.join(os4.homedir(), ".config", "ordewell", "plugins");
14142
+
14143
+ // src/plugins/pluginNames.ts
14144
+ var path12 = __toESM(require("path"));
14145
+ var PLUGIN_NAME_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
14146
+ var MAX_PLUGIN_NAME_LENGTH = 64;
14147
+ function isPlainPluginName(name) {
14148
+ return typeof name === "string" && name.length <= MAX_PLUGIN_NAME_LENGTH && PLUGIN_NAME_PATTERN.test(name);
14149
+ }
14150
+ function assertPlainPluginName(name) {
14151
+ if (!isPlainPluginName(name)) {
14152
+ throw new Error(
14153
+ `Invalid plugin name: ${JSON.stringify(name)}. Names must be a single plain segment of letters, digits, '.', '-' or '_' (max ${MAX_PLUGIN_NAME_LENGTH} characters).`
14154
+ );
14155
+ }
12333
14156
  }
14157
+ function resolvePluginInstallDir(pluginsDir, name) {
14158
+ assertPlainPluginName(name);
14159
+ const root = path12.resolve(pluginsDir);
14160
+ const dest = path12.resolve(root, name);
14161
+ const relative3 = path12.relative(root, dest);
14162
+ if (relative3 !== name || relative3 === "" || path12.isAbsolute(relative3)) {
14163
+ throw new Error(`Plugin install path escapes the plugins directory: ${JSON.stringify(name)}`);
14164
+ }
14165
+ return dest;
14166
+ }
14167
+
14168
+ // src/plugins/manifestValidation.ts
12334
14169
  function isValidManifest(obj) {
12335
14170
  if (!obj || typeof obj !== "object") return false;
12336
14171
  const m = obj;
12337
- return typeof m.name === "string" && typeof m.displayName === "string" && typeof m.description === "string" && typeof m.version === "string" && typeof m.runner === "object" && m.runner !== null && typeof m.runner.command === "string" && Array.isArray(m.runner.argsTemplate) && typeof m.features === "object" && m.features !== null && typeof m.modelDiscovery === "object" && m.modelDiscovery !== null;
14172
+ return isPlainPluginName(m.name) && typeof m.displayName === "string" && typeof m.description === "string" && typeof m.version === "string" && typeof m.runner === "object" && m.runner !== null && typeof m.runner.command === "string" && Array.isArray(m.runner.argsTemplate) && typeof m.features === "object" && m.features !== null && typeof m.modelDiscovery === "object" && m.modelDiscovery !== null;
14173
+ }
14174
+
14175
+ // src/plugins/FsPluginStore.ts
14176
+ function userPluginsDir() {
14177
+ return path13.join(os4.homedir(), ".config", "ordewell", "plugins");
12338
14178
  }
12339
14179
  function loadManifestFromDir(dir) {
12340
- const manifestPath = path11.join(dir, "manifest.json");
14180
+ const manifestPath = path13.join(dir, "manifest.json");
12341
14181
  if (!fs6.existsSync(manifestPath)) return null;
12342
14182
  try {
12343
14183
  const raw = fs6.readFileSync(manifestPath, "utf8");
@@ -12352,8 +14192,8 @@ function copyDirSync(src, dest) {
12352
14192
  ensureDir(dest);
12353
14193
  const entries = fs6.readdirSync(src, { withFileTypes: true });
12354
14194
  for (const entry of entries) {
12355
- const srcPath = path11.join(src, entry.name);
12356
- const destPath = path11.join(dest, entry.name);
14195
+ const srcPath = path13.join(src, entry.name);
14196
+ const destPath = path13.join(dest, entry.name);
12357
14197
  if (entry.isDirectory()) {
12358
14198
  copyDirSync(srcPath, destPath);
12359
14199
  } else {
@@ -12365,7 +14205,7 @@ function rmDirSync(dir) {
12365
14205
  if (!fs6.existsSync(dir)) return;
12366
14206
  const entries = fs6.readdirSync(dir, { withFileTypes: true });
12367
14207
  for (const entry of entries) {
12368
- const p = path11.join(dir, entry.name);
14208
+ const p = path13.join(dir, entry.name);
12369
14209
  if (entry.isDirectory()) {
12370
14210
  rmDirSync(p);
12371
14211
  } else {
@@ -12389,6 +14229,13 @@ var FsPluginStore = class {
12389
14229
  }
12390
14230
  return entries.filter((e) => e.isDirectory()).map((e) => e.name);
12391
14231
  }
14232
+ listDir(dir) {
14233
+ try {
14234
+ return fs6.readdirSync(dir);
14235
+ } catch {
14236
+ return [];
14237
+ }
14238
+ }
12392
14239
  loadManifest(pluginDir) {
12393
14240
  return loadManifestFromDir(pluginDir);
12394
14241
  }
@@ -12411,11 +14258,11 @@ var FsPluginStore = class {
12411
14258
  return null;
12412
14259
  }
12413
14260
  }
12414
- dirExists(path15) {
12415
- return fs6.existsSync(path15) && fs6.statSync(path15).isDirectory();
14261
+ dirExists(path17) {
14262
+ return fs6.existsSync(path17) && fs6.statSync(path17).isDirectory();
12416
14263
  }
12417
- exists(path15) {
12418
- return fs6.existsSync(path15);
14264
+ exists(path17) {
14265
+ return fs6.existsSync(path17);
12419
14266
  }
12420
14267
  };
12421
14268
 
@@ -12703,17 +14550,98 @@ var OPENCODE_MANIFEST = {
12703
14550
  ]
12704
14551
  };
12705
14552
 
14553
+ // src/plugins/pluginSource.ts
14554
+ var ALLOWED_PLUGIN_HOSTS = [
14555
+ "github.com",
14556
+ "gitlab.com",
14557
+ "bitbucket.org",
14558
+ "codeberg.org"
14559
+ ];
14560
+ var SAFE_REPO_PATH = /^\/[A-Za-z0-9._~-]+(?:\/[A-Za-z0-9._~-]+)*$/;
14561
+ var HAS_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]+:/;
14562
+ var SCP_LIKE_REMOTE = /^[^/\\]+@[^/\\]+:/;
14563
+ function hasControlCharacter(value) {
14564
+ for (let i = 0; i < value.length; i++) {
14565
+ const code = value.charCodeAt(i);
14566
+ if (code < 32 || code === 127) return true;
14567
+ }
14568
+ return false;
14569
+ }
14570
+ function assertInstallablePluginUrl(url) {
14571
+ if (typeof url !== "string" || url.trim() === "") {
14572
+ throw new Error("A plugin repository URL is required.");
14573
+ }
14574
+ if (hasControlCharacter(url)) {
14575
+ throw new Error("Plugin URL contains control characters.");
14576
+ }
14577
+ let parsed;
14578
+ try {
14579
+ parsed = new URL(url.trim());
14580
+ } catch {
14581
+ throw new Error(`Not a valid plugin repository URL: ${url}`);
14582
+ }
14583
+ if (parsed.protocol !== "https:") {
14584
+ throw new Error(
14585
+ `Plugin repositories must be cloned over https (got "${parsed.protocol}//"). git transport helpers and non-https schemes are not accepted.`
14586
+ );
14587
+ }
14588
+ if (parsed.username || parsed.password) {
14589
+ throw new Error("Plugin URL must not embed credentials.");
14590
+ }
14591
+ if (parsed.port) {
14592
+ throw new Error("Plugin URL must not specify a port.");
14593
+ }
14594
+ if (parsed.search || parsed.hash) {
14595
+ throw new Error("Plugin URL must not carry a query string or fragment.");
14596
+ }
14597
+ const host = parsed.hostname.toLowerCase();
14598
+ if (!ALLOWED_PLUGIN_HOSTS.includes(host)) {
14599
+ throw new Error(
14600
+ `Plugin host not allowed: ${host}. Allowed hosts: ${ALLOWED_PLUGIN_HOSTS.join(", ")}.`
14601
+ );
14602
+ }
14603
+ if (!SAFE_REPO_PATH.test(parsed.pathname)) {
14604
+ throw new Error(`Not a plain repository path: ${parsed.pathname}`);
14605
+ }
14606
+ return parsed;
14607
+ }
14608
+ function classifyPluginSource(source) {
14609
+ const trimmed = source.trim();
14610
+ if (trimmed.startsWith("github:")) {
14611
+ const repo = trimmed.slice("github:".length).replace(/\.git$/, "");
14612
+ return { kind: "git", url: `https://github.com/${repo}.git` };
14613
+ }
14614
+ if (HAS_SCHEME.test(trimmed) || SCP_LIKE_REMOTE.test(trimmed)) {
14615
+ return { kind: "git", url: trimmed };
14616
+ }
14617
+ return { kind: "path", path: source };
14618
+ }
14619
+
12706
14620
  // src/plugins/RunnerRegistry.ts
12707
14621
  var BUILTIN_MANIFESTS = [
12708
14622
  CLAUDE_CODE_MANIFEST,
12709
14623
  CODEX_MANIFEST,
12710
14624
  OPENCODE_MANIFEST
12711
14625
  ];
14626
+ var RESERVED_RUNNER_NAMES = new Set(BUILTIN_MANIFESTS.map((m) => m.name.toLowerCase()));
14627
+ function isReservedRunnerName(name) {
14628
+ return RESERVED_RUNNER_NAMES.has(name.toLowerCase());
14629
+ }
14630
+ function gitClone(url, destDir) {
14631
+ (0, import_child_process9.execFileSync)("git", ["clone", "--depth", "1", "--", url, destDir], {
14632
+ stdio: "pipe",
14633
+ timeout: 3e4,
14634
+ // A repository that demands credentials should fail, not hang on a prompt.
14635
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
14636
+ });
14637
+ }
12712
14638
  var RunnerRegistry = class {
12713
14639
  plugins = /* @__PURE__ */ new Map();
12714
14640
  store;
12715
- constructor(store) {
14641
+ clone;
14642
+ constructor(store, clone) {
12716
14643
  this.store = store ?? new FsPluginStore();
14644
+ this.clone = clone ?? gitClone;
12717
14645
  this.loadBuiltins();
12718
14646
  }
12719
14647
  loadBuiltins() {
@@ -12724,9 +14652,15 @@ var RunnerRegistry = class {
12724
14652
  loadUserPlugins() {
12725
14653
  const names = this.store.listUserPluginDirs();
12726
14654
  for (const name of names) {
12727
- const pluginDir = path12.join(this.store.getUserPluginsDir(), name);
14655
+ let pluginDir;
14656
+ try {
14657
+ pluginDir = resolvePluginInstallDir(this.store.getUserPluginsDir(), name);
14658
+ } catch {
14659
+ continue;
14660
+ }
12728
14661
  const manifest = this.store.loadManifest(pluginDir);
12729
- if (!manifest) continue;
14662
+ if (!manifest || !isPlainPluginName(manifest.name)) continue;
14663
+ if (isReservedRunnerName(manifest.name)) continue;
12730
14664
  this.plugins.set(manifest.name, {
12731
14665
  manifest,
12732
14666
  source: "user",
@@ -12755,7 +14689,7 @@ var RunnerRegistry = class {
12755
14689
  return entry?.source === "builtin";
12756
14690
  }
12757
14691
  installFromPath(sourcePath) {
12758
- const absPath = path12.resolve(sourcePath);
14692
+ const absPath = path14.resolve(sourcePath);
12759
14693
  if (!this.store.dirExists(absPath)) {
12760
14694
  throw new Error(`Plugin source must be a directory: ${absPath}`);
12761
14695
  }
@@ -12763,48 +14697,50 @@ var RunnerRegistry = class {
12763
14697
  if (!manifest) {
12764
14698
  throw new Error(`No valid manifest.json found in: ${absPath}`);
12765
14699
  }
12766
- const destDir = path12.join(this.store.getUserPluginsDir(), manifest.name);
12767
- this.store.ensureDir(destDir);
12768
- this.store.copyDir(absPath, destDir);
12769
- this.plugins.set(manifest.name, {
12770
- manifest,
12771
- source: "user",
12772
- installPath: destDir
12773
- });
12774
- return manifest;
14700
+ return this.registerInstalled(manifest, absPath);
12775
14701
  }
12776
14702
  installFromGit(url) {
12777
- const repoName = url.split("/").pop()?.replace(/\.git$/, "") || "plugin";
12778
- const tmpDir = path12.join(os5.tmpdir(), `ordewell-plugin-${Date.now()}`);
14703
+ const validated = assertInstallablePluginUrl(url);
14704
+ const tmpDir = path14.join(os5.tmpdir(), `ordewell-plugin-${(0, import_crypto3.randomBytes)(12).toString("hex")}`);
12779
14705
  this.store.ensureDir(tmpDir);
12780
14706
  try {
12781
- (0, import_child_process9.execSync)(`git clone --depth 1 "${url}" "${tmpDir}"`, { stdio: "pipe", timeout: 3e4 });
12782
- } catch (err) {
12783
- const message = err instanceof Error ? err.message : String(err);
12784
- throw new Error(`Failed to clone repository: ${message}`);
12785
- }
12786
- const contents = fsReaddirSync(tmpDir);
12787
- const hasManifest = contents.some((f) => f === "manifest.json");
12788
- if (!hasManifest && contents.length === 1 && this.store.dirExists(path12.join(tmpDir, contents[0]))) {
12789
- const subDir = path12.join(tmpDir, contents[0]);
12790
- return this.finishInstall(subDir, repoName, tmpDir);
12791
- }
12792
- if (!hasManifest) {
14707
+ try {
14708
+ this.clone(validated.toString(), tmpDir);
14709
+ } catch (err) {
14710
+ const message = err instanceof Error ? err.message : String(err);
14711
+ throw new Error(`Failed to clone repository: ${message}`);
14712
+ }
14713
+ const contents = this.store.listDir(tmpDir);
14714
+ const hasManifest = contents.some((f) => f === "manifest.json");
14715
+ let sourceDir = tmpDir;
14716
+ if (!hasManifest) {
14717
+ const onlyEntry = contents.length === 1 ? path14.join(tmpDir, contents[0]) : void 0;
14718
+ if (!onlyEntry || !this.store.dirExists(onlyEntry)) {
14719
+ throw new Error(`No manifest.json found in repository: ${validated.toString()}`);
14720
+ }
14721
+ sourceDir = onlyEntry;
14722
+ }
14723
+ const manifest = this.store.loadManifest(sourceDir);
14724
+ if (!manifest) {
14725
+ throw new Error("No valid manifest.json found in cloned repository");
14726
+ }
14727
+ return this.registerInstalled(manifest, sourceDir);
14728
+ } finally {
12793
14729
  this.store.removeDir(tmpDir);
12794
- throw new Error(`No manifest.json found in repository: ${url}`);
12795
14730
  }
12796
- return this.finishInstall(tmpDir, repoName, tmpDir);
12797
14731
  }
12798
- finishInstall(sourceDir, repoName, tmpDir) {
12799
- const manifest = this.store.loadManifest(sourceDir);
12800
- if (!manifest) {
12801
- this.store.removeDir(tmpDir);
12802
- throw new Error(`No valid manifest.json found in cloned repository`);
14732
+ /**
14733
+ * The single destination-building step both install routes share: the name is
14734
+ * constrained to a plain segment and the resolved destination is asserted to
14735
+ * be inside the plugins directory before anything is copied.
14736
+ */
14737
+ registerInstalled(manifest, sourceDir) {
14738
+ if (isReservedRunnerName(manifest.name)) {
14739
+ throw new Error(`Cannot install a plugin named after a built-in runner: ${manifest.name}`);
12803
14740
  }
12804
- const destDir = path12.join(this.store.getUserPluginsDir(), manifest.name);
14741
+ const destDir = resolvePluginInstallDir(this.store.getUserPluginsDir(), manifest.name);
12805
14742
  this.store.ensureDir(destDir);
12806
14743
  this.store.copyDir(sourceDir, destDir);
12807
- this.store.removeDir(tmpDir);
12808
14744
  this.plugins.set(manifest.name, {
12809
14745
  manifest,
12810
14746
  source: "user",
@@ -12825,7 +14761,8 @@ var RunnerRegistry = class {
12825
14761
  this.plugins.delete(name);
12826
14762
  }
12827
14763
  createSkeleton(name, outputDir) {
12828
- const dir = path12.resolve(outputDir, name);
14764
+ assertPlainPluginName(name);
14765
+ const dir = path14.resolve(outputDir, name);
12829
14766
  this.store.ensureDir(dir);
12830
14767
  const manifest = {
12831
14768
  name,
@@ -12857,8 +14794,8 @@ var RunnerRegistry = class {
12857
14794
  ]
12858
14795
  }
12859
14796
  };
12860
- this.store.writeFile(path12.join(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
12861
- const runSh = path12.join(dir, "run.sh");
14797
+ this.store.writeFile(path14.join(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
14798
+ const runSh = path14.join(dir, "run.sh");
12862
14799
  const scriptContent = `#!/usr/bin/env bash
12863
14800
  # Custom runner script for ${name}
12864
14801
  # Receives:
@@ -12882,13 +14819,6 @@ echo "Running ${name} with prompt: $prompt"
12882
14819
  return dir;
12883
14820
  }
12884
14821
  };
12885
- function fsReaddirSync(dir) {
12886
- try {
12887
- return fs7.readdirSync(dir);
12888
- } catch {
12889
- return [];
12890
- }
12891
- }
12892
14822
  function fsChmodSync(fpath, mode) {
12893
14823
  try {
12894
14824
  fs7.chmodSync(fpath, mode);
@@ -12898,14 +14828,14 @@ function fsChmodSync(fpath, mode) {
12898
14828
 
12899
14829
  // src/utils/stateStore.ts
12900
14830
  var fs8 = __toESM(require("fs"));
12901
- var path13 = __toESM(require("path"));
14831
+ var path15 = __toESM(require("path"));
12902
14832
  var STATE_FILE = "state.json";
12903
14833
  function getStatePath(baseDir) {
12904
- return path13.join(getStateDir(baseDir), STATE_FILE);
14834
+ return path15.join(getStateDir(baseDir), STATE_FILE);
12905
14835
  }
12906
14836
  function saveState(plan, baseDir) {
12907
14837
  const statePath = getStatePath(baseDir ?? process.cwd());
12908
- ensureDir(path13.dirname(statePath));
14838
+ ensureDir(path15.dirname(statePath));
12909
14839
  plan.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
12910
14840
  fs8.writeFileSync(statePath, JSON.stringify(plan, null, 2));
12911
14841
  }
@@ -12938,7 +14868,7 @@ function stateExists(baseDir) {
12938
14868
 
12939
14869
  // src/services/SettingsService.ts
12940
14870
  var fs9 = __toESM(require("fs"));
12941
- var path14 = __toESM(require("path"));
14871
+ var path16 = __toESM(require("path"));
12942
14872
  var os6 = __toESM(require("os"));
12943
14873
  var DEFAULTS = {
12944
14874
  grillMe: { enabled: false },
@@ -12951,7 +14881,7 @@ var DEFAULTS = {
12951
14881
  function getSettingsPath() {
12952
14882
  const override = process.env.ORDEWELL_SETTINGS_PATH;
12953
14883
  if (override && override.trim()) return override.trim();
12954
- return path14.join(os6.homedir(), ".config", "ordewell", "settings.json");
14884
+ return path16.join(os6.homedir(), ".config", "ordewell", "settings.json");
12955
14885
  }
12956
14886
  function readPlannerModels(raw) {
12957
14887
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
@@ -13116,7 +15046,7 @@ var SettingsService = class {
13116
15046
  return { ...DEFAULTS, grillMe: { ...DEFAULTS.grillMe }, tdd: { ...DEFAULTS.tdd }, prd: { ...DEFAULTS.prd }, review: { ...DEFAULTS.review }, verification: { ...DEFAULTS.verification }, researchSubagents: { ...DEFAULTS.researchSubagents } };
13117
15047
  }
13118
15048
  persist() {
13119
- const dir = path14.dirname(this.filePath);
15049
+ const dir = path16.dirname(this.filePath);
13120
15050
  if (!fs9.existsSync(dir)) {
13121
15051
  fs9.mkdirSync(dir, { recursive: true });
13122
15052
  }
@@ -13161,6 +15091,7 @@ var PlannerModelMemory = class {
13161
15091
  };
13162
15092
  // Annotate the CommonJS export names for ESM import in node:
13163
15093
  0 && (module.exports = {
15094
+ ALLOWED_PLUGIN_HOSTS,
13164
15095
  ALL_PROVIDERS,
13165
15096
  AUTO_COMMANDS,
13166
15097
  AbstractRunner,
@@ -13179,6 +15110,8 @@ var PlannerModelMemory = class {
13179
15110
  CommandLineTooLongError,
13180
15111
  ConsoleLogger,
13181
15112
  ContextCollector,
15113
+ DAEMON_SUBPROTOCOL,
15114
+ DAEMON_TOKEN_SUBPROTOCOL_PREFIX,
13182
15115
  DENY_ALL,
13183
15116
  EmbeddedNewlineError,
13184
15117
  EnvConfig,
@@ -13195,9 +15128,12 @@ var PlannerModelMemory = class {
13195
15128
  ModelResolver,
13196
15129
  OPENCODE_MANIFEST,
13197
15130
  ORCHESTRATOR_SHORTCUTS,
15131
+ ORDEWELL_SETTABLE_ENV,
13198
15132
  OpenAiService,
13199
15133
  OpenCodeAdapter,
13200
15134
  PLAN_ENVELOPE_KEY,
15135
+ PLUGIN_NAME_PATTERN,
15136
+ PROVIDER_CREDENTIAL_ENV,
13201
15137
  PROVIDER_DETECT_PRIORITY,
13202
15138
  PROVIDER_LABEL,
13203
15139
  PROVIDER_PRIORITY,
@@ -13212,6 +15148,8 @@ var PlannerModelMemory = class {
13212
15148
  RunnerInstallation,
13213
15149
  RunnerRegistry,
13214
15150
  SEARCH_EXCLUSIONS,
15151
+ SETTINGS_ENV_ALLOWLIST,
15152
+ SETTINGS_ENV_REFUSED,
13215
15153
  STATE_DIR,
13216
15154
  STOPPED_TOOL_RESULT,
13217
15155
  SYMBOL_LANGUAGES,
@@ -13224,13 +15162,19 @@ var PlannerModelMemory = class {
13224
15162
  TmuxRunner,
13225
15163
  VerdictEngine,
13226
15164
  WINDOWS_MAX_COMMAND_LINE,
15165
+ WorkspaceNotAProjectError,
13227
15166
  WorkspaceNotFoundError,
13228
15167
  addTaskToPlan,
15168
+ admitSettingsEnv,
13229
15169
  applyHeadLimit,
13230
15170
  applyTaskOps,
15171
+ assertInstallablePluginUrl,
15172
+ assertPlainPluginName,
13231
15173
  assertWorkspaceExists,
15174
+ assertWorkspaceIsProject,
13232
15175
  augmentPromptWithPriorOutputs,
13233
15176
  augmentedPath,
15177
+ bearerHeaderValue,
13234
15178
  buildConversationSystemPrompt,
13235
15179
  buildFallbackGrepArgs,
13236
15180
  buildGlobArgs,
@@ -13258,7 +15202,9 @@ var PlannerModelMemory = class {
13258
15202
  classifyCommand,
13259
15203
  classifyOutcome,
13260
15204
  classifyPlannerReply,
15205
+ classifyPluginSource,
13261
15206
  clearAugmentedPathCache,
15207
+ clearDaemonToken,
13262
15208
  clearResearchShellCache,
13263
15209
  clearState,
13264
15210
  clipboardCopyCommand,
@@ -13270,6 +15216,7 @@ var PlannerModelMemory = class {
13270
15216
  createAiService,
13271
15217
  createEmptyPlan,
13272
15218
  createTask,
15219
+ daemonTokenPath,
13273
15220
  defaultLogger,
13274
15221
  definitionPattern,
13275
15222
  deleteSession,
@@ -13280,6 +15227,7 @@ var PlannerModelMemory = class {
13280
15227
  emptyWarnings,
13281
15228
  enabledRunners,
13282
15229
  ensureDir,
15230
+ ensureStateDirIgnored,
13283
15231
  escapeControlCharsInStrings,
13284
15232
  executionLogBlock,
13285
15233
  executionSummary,
@@ -13287,6 +15235,7 @@ var PlannerModelMemory = class {
13287
15235
  extractObjectWithBalance,
13288
15236
  extractObjectsWithKey,
13289
15237
  extractPrdBlock,
15238
+ extractPresentedToken,
13290
15239
  fetchAllProviderModels,
13291
15240
  filterFallbackByAnchoredInclude,
13292
15241
  filterModelsForPrompt,
@@ -13304,6 +15253,9 @@ var PlannerModelMemory = class {
13304
15253
  isCliProvider,
13305
15254
  isExecutableResolved,
13306
15255
  isOpenAiProvider,
15256
+ isPlainPluginName,
15257
+ isReservedRunnerName,
15258
+ isValidManifest,
13307
15259
  killTree,
13308
15260
  knownModelId,
13309
15261
  languageForId,
@@ -13316,6 +15268,8 @@ var PlannerModelMemory = class {
13316
15268
  migrateLegacyPlan,
13317
15269
  migratePlanState,
13318
15270
  migrateTask,
15271
+ mintDaemonToken,
15272
+ mintSessionId,
13319
15273
  modelContextBlock,
13320
15274
  modifyValidationFeedback,
13321
15275
  normalizeAgentArgs,
@@ -13331,6 +15285,7 @@ var PlannerModelMemory = class {
13331
15285
  providerForRunner,
13332
15286
  reEmitPlanPrompt,
13333
15287
  reEmitTaskOpsPrompt,
15288
+ readDaemonToken,
13334
15289
  referencePattern,
13335
15290
  removeTaskFromPlan,
13336
15291
  renderPlanMap,
@@ -13342,6 +15297,7 @@ var PlannerModelMemory = class {
13342
15297
  resolveArgs,
13343
15298
  resolveDefaultMode,
13344
15299
  resolveModelShortcut,
15300
+ resolvePluginInstallDir,
13345
15301
  resolveProvider,
13346
15302
  resolveProviderFromPrefix,
13347
15303
  resolveResearchShell,
@@ -13372,6 +15328,8 @@ var PlannerModelMemory = class {
13372
15328
  tmuxSocketName,
13373
15329
  tmuxWindowName,
13374
15330
  toOrchestratorOptions,
15331
+ tokenSubprotocols,
15332
+ tokensMatch,
13375
15333
  truncatedPlanReEmitPrompt,
13376
15334
  updateTaskInPlan,
13377
15335
  validateModifiedPlan,