@agent-native/core 0.54.1 → 0.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +4 -0
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/connect.d.ts +3 -1
- package/dist/cli/connect.d.ts.map +1 -1
- package/dist/cli/connect.js +7 -1
- package/dist/cli/connect.js.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/plan-local.d.ts +40 -0
- package/dist/cli/plan-local.d.ts.map +1 -1
- package/dist/cli/plan-local.js +495 -9
- package/dist/cli/plan-local.js.map +1 -1
- package/dist/cli/skills.d.ts +17 -2
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +135 -36
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AgentChatHome.d.ts +23 -0
- package/dist/client/AgentChatHome.d.ts.map +1 -0
- package/dist/client/AgentChatHome.js +13 -0
- package/dist/client/AgentChatHome.js.map +1 -0
- package/dist/client/AgentPanel.d.ts +29 -2
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +116 -68
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +140 -18
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/FeedbackButton.d.ts +7 -1
- package/dist/client/FeedbackButton.d.ts.map +1 -1
- package/dist/client/FeedbackButton.js +13 -3
- package/dist/client/FeedbackButton.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts +1 -2
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-sidebar-state.d.ts +2 -0
- package/dist/client/agent-sidebar-state.d.ts.map +1 -1
- package/dist/client/agent-sidebar-state.js +15 -4
- package/dist/client/agent-sidebar-state.js.map +1 -1
- package/dist/client/chat/index.d.ts +5 -0
- package/dist/client/chat/index.d.ts.map +1 -1
- package/dist/client/chat/index.js +5 -0
- package/dist/client/chat/index.js.map +1 -1
- package/dist/client/chat/run-recovery.d.ts.map +1 -1
- package/dist/client/chat/run-recovery.js +4 -6
- package/dist/client/chat/run-recovery.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts +1 -0
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +16 -0
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/chat/tool-render-registry.d.ts +24 -0
- package/dist/client/chat/tool-render-registry.d.ts.map +1 -0
- package/dist/client/chat/tool-render-registry.js +37 -0
- package/dist/client/chat/tool-render-registry.js.map +1 -0
- package/dist/client/chat/widgets/DataChartRenderer.d.ts +5 -0
- package/dist/client/chat/widgets/DataChartRenderer.d.ts.map +1 -0
- package/dist/client/chat/widgets/DataChartRenderer.js +33 -0
- package/dist/client/chat/widgets/DataChartRenderer.js.map +1 -0
- package/dist/client/chat/widgets/DataChartWidget.d.ts +5 -0
- package/dist/client/chat/widgets/DataChartWidget.d.ts.map +1 -0
- package/dist/client/chat/widgets/DataChartWidget.js +15 -0
- package/dist/client/chat/widgets/DataChartWidget.js.map +1 -0
- package/dist/client/chat/widgets/DataInsightsWidget.d.ts +5 -0
- package/dist/client/chat/widgets/DataInsightsWidget.d.ts.map +1 -0
- package/dist/client/chat/widgets/DataInsightsWidget.js +18 -0
- package/dist/client/chat/widgets/DataInsightsWidget.js.map +1 -0
- package/dist/client/chat/widgets/DataTableWidget.d.ts +9 -0
- package/dist/client/chat/widgets/DataTableWidget.d.ts.map +1 -0
- package/dist/client/chat/widgets/DataTableWidget.js +95 -0
- package/dist/client/chat/widgets/DataTableWidget.js.map +1 -0
- package/dist/client/chat/widgets/builtin-tool-renderers.d.ts +2 -0
- package/dist/client/chat/widgets/builtin-tool-renderers.d.ts.map +1 -0
- package/dist/client/chat/widgets/builtin-tool-renderers.js +27 -0
- package/dist/client/chat/widgets/builtin-tool-renderers.js.map +1 -0
- package/dist/client/chat/widgets/data-widget-types.d.ts +52 -0
- package/dist/client/chat/widgets/data-widget-types.d.ts.map +1 -0
- package/dist/client/chat/widgets/data-widget-types.js +93 -0
- package/dist/client/chat/widgets/data-widget-types.js.map +1 -0
- package/dist/client/chat-view-transition.d.ts +23 -0
- package/dist/client/chat-view-transition.d.ts.map +1 -0
- package/dist/client/chat-view-transition.js +50 -0
- package/dist/client/chat-view-transition.js.map +1 -0
- package/dist/client/composer/PromptComposer.d.ts +1 -1
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +2 -2
- package/dist/client/composer/PromptComposer.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +2 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +2 -1
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/index.d.ts +5 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +5 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/route-state.d.ts +6 -0
- package/dist/client/route-state.d.ts.map +1 -1
- package/dist/client/route-state.js +29 -1
- package/dist/client/route-state.js.map +1 -1
- package/dist/client/use-chat-threads.d.ts.map +1 -1
- package/dist/client/use-chat-threads.js +19 -4
- package/dist/client/use-chat-threads.js.map +1 -1
- package/dist/scripts/dev/index.d.ts +1 -0
- package/dist/scripts/dev/index.d.ts.map +1 -1
- package/dist/scripts/dev/index.js +129 -127
- package/dist/scripts/dev/index.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +8 -0
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +29 -21
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/prompts/framework-core-compact.d.ts +4 -1
- package/dist/server/prompts/framework-core-compact.d.ts.map +1 -1
- package/dist/server/prompts/framework-core-compact.js +11 -4
- package/dist/server/prompts/framework-core-compact.js.map +1 -1
- package/dist/server/prompts/framework-core.d.ts +4 -1
- package/dist/server/prompts/framework-core.d.ts.map +1 -1
- package/dist/server/prompts/framework-core.js +15 -5
- package/dist/server/prompts/framework-core.js.map +1 -1
- package/dist/server/prompts/shared-rules.d.ts +4 -1
- package/dist/server/prompts/shared-rules.d.ts.map +1 -1
- package/dist/server/prompts/shared-rules.js +4 -1
- package/dist/server/prompts/shared-rules.js.map +1 -1
- package/dist/styles/agent-native.css +55 -0
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +4 -0
- package/dist/vite/client.js.map +1 -1
- package/docs/content/external-agents.md +14 -4
- package/docs/content/getting-started.md +1 -0
- package/docs/content/key-concepts.md +34 -15
- package/docs/content/mcp-apps.md +2 -0
- package/docs/content/mcp-protocol.md +2 -2
- package/docs/content/template-plan.md +16 -1
- package/docs/content/what-is-agent-native.md +10 -2
- package/package.json +1 -1
package/dist/cli/plan-local.js
CHANGED
|
@@ -205,10 +205,12 @@ function repoRelativePlanPath(dir) {
|
|
|
205
205
|
function localPlanBridgePageUrl(input) {
|
|
206
206
|
return `${normalizeBridgeAppUrl(input.appUrl)}/local-plans/${encodeURIComponent(path.basename(path.resolve(input.dir)))}?bridge=${encodeURIComponent(input.bridgeUrl)}`;
|
|
207
207
|
}
|
|
208
|
-
function
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
208
|
+
function writeLocalPlanUrlFile(dir, url, urlFile) {
|
|
209
|
+
const file = path.resolve(urlFile || path.join(dir, ".plan-url"));
|
|
210
|
+
fs.writeFileSync(file, `${url}\n`, { encoding: "utf-8", mode: 0o600 });
|
|
211
|
+
return file;
|
|
212
|
+
}
|
|
213
|
+
function runOpenCommand(command, args) {
|
|
212
214
|
const result = spawnSync(command, args, {
|
|
213
215
|
stdio: "ignore",
|
|
214
216
|
windowsHide: true,
|
|
@@ -230,6 +232,26 @@ function openLocalUrl(url) {
|
|
|
230
232
|
}
|
|
231
233
|
return { ok: true, command: commandDisplay };
|
|
232
234
|
}
|
|
235
|
+
function openLocalUrl(url) {
|
|
236
|
+
const platform = process.platform;
|
|
237
|
+
if (platform === "darwin") {
|
|
238
|
+
for (const appName of [
|
|
239
|
+
"Google Chrome",
|
|
240
|
+
"Chromium",
|
|
241
|
+
"Microsoft Edge",
|
|
242
|
+
"Brave Browser",
|
|
243
|
+
]) {
|
|
244
|
+
const result = runOpenCommand("open", ["-a", appName, url]);
|
|
245
|
+
if (result.ok)
|
|
246
|
+
return result;
|
|
247
|
+
}
|
|
248
|
+
return runOpenCommand("open", [url]);
|
|
249
|
+
}
|
|
250
|
+
if (platform === "win32") {
|
|
251
|
+
return runOpenCommand("cmd", ["/c", "start", "", url]);
|
|
252
|
+
}
|
|
253
|
+
return runOpenCommand("xdg-open", [url]);
|
|
254
|
+
}
|
|
233
255
|
function escapeHtml(value) {
|
|
234
256
|
return value
|
|
235
257
|
.replace(/&/g, "&")
|
|
@@ -629,6 +651,333 @@ function lintColumnsBlocks(file, source, issues) {
|
|
|
629
651
|
}
|
|
630
652
|
}
|
|
631
653
|
}
|
|
654
|
+
function findBalancedEnd(source, start, open, close) {
|
|
655
|
+
let depth = 0;
|
|
656
|
+
let quote = null;
|
|
657
|
+
for (let i = start; i < source.length; i += 1) {
|
|
658
|
+
const char = source[i];
|
|
659
|
+
if (quote) {
|
|
660
|
+
if (char === "\\" && i + 1 < source.length) {
|
|
661
|
+
i += 1;
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
if (char === quote)
|
|
665
|
+
quote = null;
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
669
|
+
quote = char;
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
if (char === open) {
|
|
673
|
+
depth += 1;
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
if (char === close) {
|
|
677
|
+
depth -= 1;
|
|
678
|
+
if (depth === 0)
|
|
679
|
+
return i;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return -1;
|
|
683
|
+
}
|
|
684
|
+
function readJsxAttribute(opening, name) {
|
|
685
|
+
let i = 0;
|
|
686
|
+
while (i < opening.length) {
|
|
687
|
+
if (!/[A-Za-z_:]/.test(opening[i] ?? "")) {
|
|
688
|
+
i += 1;
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
const nameStart = i;
|
|
692
|
+
i += 1;
|
|
693
|
+
while (/[\w:.-]/.test(opening[i] ?? ""))
|
|
694
|
+
i += 1;
|
|
695
|
+
const attrName = opening.slice(nameStart, i);
|
|
696
|
+
while (/\s/.test(opening[i] ?? ""))
|
|
697
|
+
i += 1;
|
|
698
|
+
if (opening[i] !== "=") {
|
|
699
|
+
if (attrName === name) {
|
|
700
|
+
return { name, kind: "boolean", value: "true", start: nameStart };
|
|
701
|
+
}
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
i += 1;
|
|
705
|
+
while (/\s/.test(opening[i] ?? ""))
|
|
706
|
+
i += 1;
|
|
707
|
+
const valueStart = i;
|
|
708
|
+
const quote = opening[i];
|
|
709
|
+
if (quote === '"' || quote === "'" || quote === "`") {
|
|
710
|
+
i += 1;
|
|
711
|
+
while (i < opening.length) {
|
|
712
|
+
if (opening[i] === "\\" && i + 1 < opening.length) {
|
|
713
|
+
i += 2;
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
if (opening[i] === quote)
|
|
717
|
+
break;
|
|
718
|
+
i += 1;
|
|
719
|
+
}
|
|
720
|
+
const value = opening.slice(valueStart + 1, i);
|
|
721
|
+
i += 1;
|
|
722
|
+
if (attrName === name) {
|
|
723
|
+
return { name, kind: "string", value, start: valueStart + 1 };
|
|
724
|
+
}
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
if (quote === "{") {
|
|
728
|
+
const end = findBalancedEnd(opening, i, "{", "}");
|
|
729
|
+
if (end < 0) {
|
|
730
|
+
if (attrName === name) {
|
|
731
|
+
return {
|
|
732
|
+
name,
|
|
733
|
+
kind: "expression",
|
|
734
|
+
value: opening.slice(valueStart + 1),
|
|
735
|
+
start: valueStart + 1,
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
break;
|
|
739
|
+
}
|
|
740
|
+
const value = opening.slice(valueStart + 1, end);
|
|
741
|
+
i = end + 1;
|
|
742
|
+
if (attrName === name) {
|
|
743
|
+
return { name, kind: "expression", value, start: valueStart + 1 };
|
|
744
|
+
}
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
while (i < opening.length && !/[\s>]/.test(opening[i] ?? ""))
|
|
748
|
+
i += 1;
|
|
749
|
+
if (attrName === name) {
|
|
750
|
+
return {
|
|
751
|
+
name,
|
|
752
|
+
kind: "bare",
|
|
753
|
+
value: opening.slice(valueStart, i),
|
|
754
|
+
start: valueStart,
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
return null;
|
|
759
|
+
}
|
|
760
|
+
function expressionOffset(expression) {
|
|
761
|
+
return expression.search(/\S/);
|
|
762
|
+
}
|
|
763
|
+
function extractTopLevelObjectLiterals(expression) {
|
|
764
|
+
const leading = expressionOffset(expression);
|
|
765
|
+
if (leading < 0 || expression[leading] !== "[")
|
|
766
|
+
return null;
|
|
767
|
+
const arrayEnd = findBalancedEnd(expression, leading, "[", "]");
|
|
768
|
+
if (arrayEnd < 0)
|
|
769
|
+
return null;
|
|
770
|
+
const objects = [];
|
|
771
|
+
let i = leading + 1;
|
|
772
|
+
while (i < arrayEnd) {
|
|
773
|
+
const char = expression[i];
|
|
774
|
+
if (/\s|,/.test(char ?? "")) {
|
|
775
|
+
i += 1;
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
if (char !== "{") {
|
|
779
|
+
return null;
|
|
780
|
+
}
|
|
781
|
+
const objectEnd = findBalancedEnd(expression, i, "{", "}");
|
|
782
|
+
if (objectEnd < 0 || objectEnd > arrayEnd)
|
|
783
|
+
return null;
|
|
784
|
+
objects.push({
|
|
785
|
+
source: expression.slice(i, objectEnd + 1),
|
|
786
|
+
start: i,
|
|
787
|
+
});
|
|
788
|
+
i = objectEnd + 1;
|
|
789
|
+
}
|
|
790
|
+
return objects;
|
|
791
|
+
}
|
|
792
|
+
function findValueEnd(source, start) {
|
|
793
|
+
let i = start;
|
|
794
|
+
let quote = null;
|
|
795
|
+
let depth = 0;
|
|
796
|
+
while (i < source.length) {
|
|
797
|
+
const char = source[i];
|
|
798
|
+
if (quote) {
|
|
799
|
+
if (char === "\\" && i + 1 < source.length) {
|
|
800
|
+
i += 2;
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
if (char === quote)
|
|
804
|
+
quote = null;
|
|
805
|
+
i += 1;
|
|
806
|
+
continue;
|
|
807
|
+
}
|
|
808
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
809
|
+
quote = char;
|
|
810
|
+
i += 1;
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
if (char === "{" || char === "[" || char === "(") {
|
|
814
|
+
depth += 1;
|
|
815
|
+
i += 1;
|
|
816
|
+
continue;
|
|
817
|
+
}
|
|
818
|
+
if (char === "}" || char === "]" || char === ")") {
|
|
819
|
+
if (depth === 0)
|
|
820
|
+
return i;
|
|
821
|
+
depth -= 1;
|
|
822
|
+
i += 1;
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
if (char === "," && depth === 0)
|
|
826
|
+
return i;
|
|
827
|
+
i += 1;
|
|
828
|
+
}
|
|
829
|
+
return source.length;
|
|
830
|
+
}
|
|
831
|
+
function readObjectKey(source, start) {
|
|
832
|
+
let i = start;
|
|
833
|
+
while (/\s|,/.test(source[i] ?? ""))
|
|
834
|
+
i += 1;
|
|
835
|
+
const keyStart = i;
|
|
836
|
+
const quote = source[i];
|
|
837
|
+
let key = "";
|
|
838
|
+
if (quote === '"' || quote === "'") {
|
|
839
|
+
i += 1;
|
|
840
|
+
const valueStart = i;
|
|
841
|
+
while (i < source.length) {
|
|
842
|
+
if (source[i] === "\\" && i + 1 < source.length) {
|
|
843
|
+
i += 2;
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
if (source[i] === quote)
|
|
847
|
+
break;
|
|
848
|
+
i += 1;
|
|
849
|
+
}
|
|
850
|
+
key = source.slice(valueStart, i);
|
|
851
|
+
i += 1;
|
|
852
|
+
}
|
|
853
|
+
else if (/[A-Za-z_$]/.test(quote ?? "")) {
|
|
854
|
+
i += 1;
|
|
855
|
+
while (/[\w$]/.test(source[i] ?? ""))
|
|
856
|
+
i += 1;
|
|
857
|
+
key = source.slice(keyStart, i);
|
|
858
|
+
}
|
|
859
|
+
else {
|
|
860
|
+
return null;
|
|
861
|
+
}
|
|
862
|
+
while (/\s/.test(source[i] ?? ""))
|
|
863
|
+
i += 1;
|
|
864
|
+
if (source[i] !== ":")
|
|
865
|
+
return null;
|
|
866
|
+
return { key, keyStart, colon: i };
|
|
867
|
+
}
|
|
868
|
+
function readTopLevelObjectProperty(objectSource, name) {
|
|
869
|
+
const body = objectSource.trim().startsWith("{")
|
|
870
|
+
? objectSource.slice(1, objectSource.lastIndexOf("}"))
|
|
871
|
+
: objectSource;
|
|
872
|
+
let i = 0;
|
|
873
|
+
while (i < body.length) {
|
|
874
|
+
const key = readObjectKey(body, i);
|
|
875
|
+
if (!key) {
|
|
876
|
+
i += 1;
|
|
877
|
+
continue;
|
|
878
|
+
}
|
|
879
|
+
const valueStart = key.colon + 1;
|
|
880
|
+
const valueEnd = findValueEnd(body, valueStart);
|
|
881
|
+
if (key.key === name) {
|
|
882
|
+
return { value: body.slice(valueStart, valueEnd), valueStart };
|
|
883
|
+
}
|
|
884
|
+
i = valueEnd + 1;
|
|
885
|
+
}
|
|
886
|
+
return null;
|
|
887
|
+
}
|
|
888
|
+
function isStaticNonEmptyStringLiteral(value) {
|
|
889
|
+
const trimmed = value.trim();
|
|
890
|
+
const quote = trimmed[0];
|
|
891
|
+
if (quote !== '"' && quote !== "'" && quote !== "`")
|
|
892
|
+
return false;
|
|
893
|
+
if (!trimmed.endsWith(quote))
|
|
894
|
+
return false;
|
|
895
|
+
if (quote === "`" && /\$\{/.test(trimmed))
|
|
896
|
+
return false;
|
|
897
|
+
return trimmed.slice(1, -1).trim().length > 0;
|
|
898
|
+
}
|
|
899
|
+
function hasRequiredStaticId(objectSource) {
|
|
900
|
+
const prop = readTopLevelObjectProperty(objectSource, "id");
|
|
901
|
+
return prop ? isStaticNonEmptyStringLiteral(prop.value) : false;
|
|
902
|
+
}
|
|
903
|
+
function lintChecklistShape(file, source, issues) {
|
|
904
|
+
const scanSource = maskCodeRegions(source);
|
|
905
|
+
const re = /<Checklist\b/g;
|
|
906
|
+
let match;
|
|
907
|
+
while ((match = re.exec(scanSource))) {
|
|
908
|
+
const start = match.index;
|
|
909
|
+
const openingEnd = findJsxOpeningTagEnd(scanSource, start);
|
|
910
|
+
if (openingEnd < 0)
|
|
911
|
+
continue;
|
|
912
|
+
const opening = source.slice(start, openingEnd + 1);
|
|
913
|
+
const items = readJsxAttribute(opening, "items");
|
|
914
|
+
if (!items)
|
|
915
|
+
continue;
|
|
916
|
+
if (items.kind !== "expression") {
|
|
917
|
+
addValidationIssue(issues, file, source, start + items.start, "Checklist items must be an inline array expression with stable item ids.");
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
const objects = extractTopLevelObjectLiterals(items.value);
|
|
921
|
+
if (!objects) {
|
|
922
|
+
addValidationIssue(issues, file, source, start + items.start, "Checklist items must be an inline array of object literals so local check can validate the renderer schema.");
|
|
923
|
+
continue;
|
|
924
|
+
}
|
|
925
|
+
objects.forEach((item, index) => {
|
|
926
|
+
if (hasRequiredStaticId(item.source))
|
|
927
|
+
return;
|
|
928
|
+
addValidationIssue(issues, file, source, start + items.start + item.start, `Checklist items[${index}].id is required by the Plan renderer schema; add a stable string id.`);
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
function lintQuestionFormShape(file, source, issues) {
|
|
933
|
+
const scanSource = maskCodeRegions(source);
|
|
934
|
+
const re = /<(QuestionForm|VisualQuestions)\b/g;
|
|
935
|
+
let match;
|
|
936
|
+
while ((match = re.exec(scanSource))) {
|
|
937
|
+
const start = match.index;
|
|
938
|
+
const tag = match[1] ?? "QuestionForm";
|
|
939
|
+
const openingEnd = findJsxOpeningTagEnd(scanSource, start);
|
|
940
|
+
if (openingEnd < 0)
|
|
941
|
+
continue;
|
|
942
|
+
const opening = source.slice(start, openingEnd + 1);
|
|
943
|
+
const questions = readJsxAttribute(opening, "questions");
|
|
944
|
+
if (!questions) {
|
|
945
|
+
addValidationIssue(issues, file, source, start, `${tag} requires a questions array with at least one question object.`);
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
948
|
+
if (questions.kind !== "expression") {
|
|
949
|
+
addValidationIssue(issues, file, source, start + questions.start, `${tag} questions must be an inline array expression with stable question ids.`);
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
const questionObjects = extractTopLevelObjectLiterals(questions.value);
|
|
953
|
+
if (!questionObjects || questionObjects.length === 0) {
|
|
954
|
+
addValidationIssue(issues, file, source, start + questions.start, `${tag} questions must be a non-empty inline array of object literals.`);
|
|
955
|
+
continue;
|
|
956
|
+
}
|
|
957
|
+
questionObjects.forEach((question, questionIndex) => {
|
|
958
|
+
if (!hasRequiredStaticId(question.source)) {
|
|
959
|
+
addValidationIssue(issues, file, source, start + questions.start + question.start, `${tag} questions[${questionIndex}].id is required by the Plan renderer schema; add a stable string id.`);
|
|
960
|
+
}
|
|
961
|
+
const options = readTopLevelObjectProperty(question.source, "options");
|
|
962
|
+
if (!options)
|
|
963
|
+
return;
|
|
964
|
+
const optionObjects = extractTopLevelObjectLiterals(options.value);
|
|
965
|
+
if (!optionObjects) {
|
|
966
|
+
addValidationIssue(issues, file, source, start + questions.start + question.start + options.valueStart, `${tag} questions[${questionIndex}].options must be an inline array of object literals.`);
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
optionObjects.forEach((option, optionIndex) => {
|
|
970
|
+
if (hasRequiredStaticId(option.source))
|
|
971
|
+
return;
|
|
972
|
+
addValidationIssue(issues, file, source, start +
|
|
973
|
+
questions.start +
|
|
974
|
+
question.start +
|
|
975
|
+
options.valueStart +
|
|
976
|
+
option.start, `${tag} questions[${questionIndex}].options[${optionIndex}].id is required by the Plan renderer schema; add a stable string id.`);
|
|
977
|
+
});
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
}
|
|
632
981
|
// Blank out fenced code blocks and inline code spans (preserving newlines and
|
|
633
982
|
// length) so block-tag linters don't trip on documentation examples written in
|
|
634
983
|
// prose — e.g. an inline `<WireframeBlock><Screen>...</Screen></WireframeBlock>`
|
|
@@ -645,6 +994,8 @@ export function validateLocalPlanFiles(files) {
|
|
|
645
994
|
const source = maskCodeRegions(entry.source);
|
|
646
995
|
lintWireframeBlocks(entry.file, source, issues);
|
|
647
996
|
lintColumnsBlocks(entry.file, source, issues);
|
|
997
|
+
lintChecklistShape(entry.file, entry.source, issues);
|
|
998
|
+
lintQuestionFormShape(entry.file, entry.source, issues);
|
|
648
999
|
}
|
|
649
1000
|
return issues;
|
|
650
1001
|
}
|
|
@@ -921,6 +1272,9 @@ export async function startLocalPlanBridge(input) {
|
|
|
921
1272
|
}
|
|
922
1273
|
const bridgeUrl = `http://${bridgeHostForUrl(host)}:${address.port}/local-plan.json?token=${encodeURIComponent(token)}`;
|
|
923
1274
|
const url = localPlanBridgePageUrl({ dir, bridgeUrl, appUrl });
|
|
1275
|
+
const urlFile = input.urlFile === false
|
|
1276
|
+
? undefined
|
|
1277
|
+
: writeLocalPlanUrlFile(dir, url, input.urlFile);
|
|
924
1278
|
const openResult = input.open
|
|
925
1279
|
? (input.openUrl || openLocalUrl)(url)
|
|
926
1280
|
: undefined;
|
|
@@ -930,6 +1284,7 @@ export async function startLocalPlanBridge(input) {
|
|
|
930
1284
|
ok: true,
|
|
931
1285
|
dir,
|
|
932
1286
|
url,
|
|
1287
|
+
...(urlFile ? { urlFile } : {}),
|
|
933
1288
|
bridgeUrl,
|
|
934
1289
|
appUrl,
|
|
935
1290
|
title: initialPayload.title,
|
|
@@ -947,6 +1302,94 @@ export async function startLocalPlanBridge(input) {
|
|
|
947
1302
|
},
|
|
948
1303
|
};
|
|
949
1304
|
}
|
|
1305
|
+
function localPlanBridgeWarnings(input) {
|
|
1306
|
+
const warnings = [];
|
|
1307
|
+
try {
|
|
1308
|
+
const appUrl = new URL(input.appUrl);
|
|
1309
|
+
const bridgeUrl = new URL(input.bridgeUrl);
|
|
1310
|
+
if (appUrl.protocol === "https:" && bridgeUrl.protocol === "http:") {
|
|
1311
|
+
warnings.push("Safari may block the hosted HTTPS Plan UI from reading the HTTP localhost bridge. Use Chrome/Chromium/Edge, or pass --app-url http://localhost:8096 when running a local Plan app.");
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
catch {
|
|
1315
|
+
// The URLs were normalized earlier; ignore defensive parse failures.
|
|
1316
|
+
}
|
|
1317
|
+
return warnings;
|
|
1318
|
+
}
|
|
1319
|
+
export async function verifyLocalPlanBridge(input) {
|
|
1320
|
+
const fetchFn = input.fetchFn ?? fetch;
|
|
1321
|
+
const bridge = await startLocalPlanBridge({
|
|
1322
|
+
dir: input.dir,
|
|
1323
|
+
kind: input.kind,
|
|
1324
|
+
title: input.title,
|
|
1325
|
+
brief: input.brief,
|
|
1326
|
+
appUrl: input.appUrl,
|
|
1327
|
+
host: input.host,
|
|
1328
|
+
port: input.port,
|
|
1329
|
+
token: input.token,
|
|
1330
|
+
urlFile: input.urlFile,
|
|
1331
|
+
});
|
|
1332
|
+
try {
|
|
1333
|
+
const preflight = await fetchFn(bridge.result.bridgeUrl, {
|
|
1334
|
+
method: "OPTIONS",
|
|
1335
|
+
headers: {
|
|
1336
|
+
origin: bridge.result.appUrl,
|
|
1337
|
+
"access-control-request-method": "GET",
|
|
1338
|
+
"access-control-request-private-network": "true",
|
|
1339
|
+
},
|
|
1340
|
+
});
|
|
1341
|
+
const response = await fetchFn(bridge.result.bridgeUrl, {
|
|
1342
|
+
method: "GET",
|
|
1343
|
+
headers: { accept: "application/json" },
|
|
1344
|
+
});
|
|
1345
|
+
const payload = (await response.json().catch(() => null));
|
|
1346
|
+
const mdxFiles = payload?.mdx
|
|
1347
|
+
? Object.keys(payload.mdx).filter((file) => file !== "assets/")
|
|
1348
|
+
: undefined;
|
|
1349
|
+
const bridgeOk = response.ok &&
|
|
1350
|
+
payload?.ok === true &&
|
|
1351
|
+
payload.source === "agent-native-local-bridge" &&
|
|
1352
|
+
payload.localOnly === true &&
|
|
1353
|
+
Boolean(payload.mdx?.["plan.mdx"]);
|
|
1354
|
+
const preflightOk = preflight.status === 204 &&
|
|
1355
|
+
preflight.headers.get("access-control-allow-origin") === "*" &&
|
|
1356
|
+
preflight.headers.get("access-control-allow-private-network") === "true";
|
|
1357
|
+
return {
|
|
1358
|
+
ok: bridgeOk && preflightOk,
|
|
1359
|
+
dir: bridge.result.dir,
|
|
1360
|
+
url: bridge.result.url,
|
|
1361
|
+
...(bridge.result.urlFile ? { urlFile: bridge.result.urlFile } : {}),
|
|
1362
|
+
bridgeUrl: bridge.result.bridgeUrl,
|
|
1363
|
+
appUrl: bridge.result.appUrl,
|
|
1364
|
+
title: bridge.result.title,
|
|
1365
|
+
kind: bridge.result.kind,
|
|
1366
|
+
files: bridge.result.files,
|
|
1367
|
+
preflight: {
|
|
1368
|
+
status: preflight.status,
|
|
1369
|
+
allowOrigin: preflight.headers.get("access-control-allow-origin"),
|
|
1370
|
+
allowPrivateNetwork: preflight.headers.get("access-control-allow-private-network"),
|
|
1371
|
+
},
|
|
1372
|
+
bridge: {
|
|
1373
|
+
status: response.status,
|
|
1374
|
+
ok: bridgeOk,
|
|
1375
|
+
...(payload?.source ? { source: payload.source } : {}),
|
|
1376
|
+
...(typeof payload?.localOnly === "boolean"
|
|
1377
|
+
? { localOnly: payload.localOnly }
|
|
1378
|
+
: {}),
|
|
1379
|
+
...(Array.isArray(payload?.files) ? { files: payload.files } : {}),
|
|
1380
|
+
...(mdxFiles ? { mdxFiles } : {}),
|
|
1381
|
+
...(payload?.error ? { error: payload.error } : {}),
|
|
1382
|
+
},
|
|
1383
|
+
warnings: localPlanBridgeWarnings({
|
|
1384
|
+
appUrl: bridge.result.appUrl,
|
|
1385
|
+
bridgeUrl: bridge.result.bridgeUrl,
|
|
1386
|
+
}),
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
finally {
|
|
1390
|
+
await new Promise((resolve) => bridge.server.close(() => resolve()));
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
950
1393
|
function writeLocalPlanSkeleton(input) {
|
|
951
1394
|
const dir = path.resolve(input.dir || path.join(defaultPlansDir(), localPlanFolderName(input.title)));
|
|
952
1395
|
const planPath = path.join(dir, "plan.mdx");
|
|
@@ -1059,12 +1502,25 @@ async function runServe(args) {
|
|
|
1059
1502
|
host: optionalArg(args, "host"),
|
|
1060
1503
|
port,
|
|
1061
1504
|
open: boolArg(args, "open"),
|
|
1505
|
+
urlFile: optionalArg(args, "url-file") || optionalArg(args, "out"),
|
|
1062
1506
|
kind: optionalArg(args, "kind")
|
|
1063
1507
|
? normalizeKind(optionalArg(args, "kind"))
|
|
1064
1508
|
: undefined,
|
|
1065
1509
|
});
|
|
1066
1510
|
process.stdout.write(`${JSON.stringify(bridge.result, null, 2)}\n`);
|
|
1067
|
-
process.stderr.write(
|
|
1511
|
+
process.stderr.write([
|
|
1512
|
+
`Local Plan bridge running at ${bridge.result.bridgeUrl}`,
|
|
1513
|
+
bridge.result.urlFile
|
|
1514
|
+
? `Open URL written to ${bridge.result.urlFile}`
|
|
1515
|
+
: "",
|
|
1516
|
+
...localPlanBridgeWarnings({
|
|
1517
|
+
appUrl: bridge.result.appUrl,
|
|
1518
|
+
bridgeUrl: bridge.result.bridgeUrl,
|
|
1519
|
+
}),
|
|
1520
|
+
"Press Ctrl+C to stop.",
|
|
1521
|
+
]
|
|
1522
|
+
.filter(Boolean)
|
|
1523
|
+
.join("\n") + "\n");
|
|
1068
1524
|
await new Promise((resolve) => {
|
|
1069
1525
|
const stop = () => {
|
|
1070
1526
|
bridge.server.close(() => resolve());
|
|
@@ -1073,6 +1529,28 @@ async function runServe(args) {
|
|
|
1073
1529
|
process.once("SIGTERM", stop);
|
|
1074
1530
|
});
|
|
1075
1531
|
}
|
|
1532
|
+
async function runVerify(args) {
|
|
1533
|
+
const portValue = optionalArg(args, "port");
|
|
1534
|
+
const port = portValue ? Number(portValue) : undefined;
|
|
1535
|
+
if (portValue && (!Number.isInteger(port) || port < 0 || port > 65535)) {
|
|
1536
|
+
throw new Error("--port must be an integer between 0 and 65535.");
|
|
1537
|
+
}
|
|
1538
|
+
const result = await verifyLocalPlanBridge({
|
|
1539
|
+
dir: stringArg(args, "dir"),
|
|
1540
|
+
appUrl: optionalArg(args, "app-url"),
|
|
1541
|
+
title: optionalArg(args, "title"),
|
|
1542
|
+
brief: optionalArg(args, "brief"),
|
|
1543
|
+
host: optionalArg(args, "host"),
|
|
1544
|
+
port,
|
|
1545
|
+
urlFile: optionalArg(args, "url-file") || optionalArg(args, "out") || false,
|
|
1546
|
+
kind: optionalArg(args, "kind")
|
|
1547
|
+
? normalizeKind(optionalArg(args, "kind"))
|
|
1548
|
+
: undefined,
|
|
1549
|
+
});
|
|
1550
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
1551
|
+
if (!result.ok)
|
|
1552
|
+
process.exit(1);
|
|
1553
|
+
}
|
|
1076
1554
|
async function runBlocks(args) {
|
|
1077
1555
|
const format = normalizePlanBlockFormat(optionalArg(args, "format"));
|
|
1078
1556
|
const appUrl = optionalArg(args, "app-url") ||
|
|
@@ -1125,7 +1603,8 @@ Usage:
|
|
|
1125
1603
|
agent-native plan blocks [--format reference|schema] [--app-url <url>] [--out <file>] [--json]
|
|
1126
1604
|
agent-native plan local init --title <title> [--brief <text>] [--kind plan|recap] [--dir <folder>] [--force]
|
|
1127
1605
|
agent-native plan local check --dir <folder>
|
|
1128
|
-
agent-native plan local serve --dir <folder> [--app-url <url>] [--kind plan|recap] [--open] [--port <port>]
|
|
1606
|
+
agent-native plan local serve --dir <folder> [--app-url <url>] [--kind plan|recap] [--open] [--port <port>] [--url-file <file>]
|
|
1607
|
+
agent-native plan local verify --dir <folder> [--app-url <url>] [--kind plan|recap] [--port <port>]
|
|
1129
1608
|
agent-native plan local preview --dir <folder> [--app-url <url>] [--kind plan|recap] [--open] [--out preview.html]
|
|
1130
1609
|
|
|
1131
1610
|
The blocks command fetches the no-auth, read-only get-plan-blocks catalog from
|
|
@@ -1146,9 +1625,13 @@ Common flow:
|
|
|
1146
1625
|
|
|
1147
1626
|
\`plan local serve\` starts a tiny localhost bridge and opens the hosted Plan UI
|
|
1148
1627
|
against that local-only source. The hosted app fetches the MDX from localhost in
|
|
1149
|
-
the browser; it does not write plan content to the hosted database.
|
|
1150
|
-
|
|
1151
|
-
|
|
1628
|
+
the browser; it does not write plan content to the hosted database. The served
|
|
1629
|
+
URL is written to \`.plan-url\` by default; pass \`--url-file\` to choose a
|
|
1630
|
+
different local-only file. On macOS, \`--open\` prefers Chromium browsers because
|
|
1631
|
+
Safari may block the hosted HTTPS page from reading the HTTP localhost bridge.
|
|
1632
|
+
Use \`plan local verify\` for headless bridge/CORS diagnostics that exit cleanly.
|
|
1633
|
+
Use \`plan local preview\` for a local Plan dev server route. \`preview --out\` is
|
|
1634
|
+
a legacy/debug escape hatch that writes a standalone static HTML file.
|
|
1152
1635
|
`;
|
|
1153
1636
|
export async function runPlan(argv) {
|
|
1154
1637
|
const [area, sub, ...rest] = argv;
|
|
@@ -1193,6 +1676,9 @@ export async function runPlan(argv) {
|
|
|
1193
1676
|
case "serve":
|
|
1194
1677
|
await runServe(args);
|
|
1195
1678
|
return;
|
|
1679
|
+
case "verify":
|
|
1680
|
+
await runVerify(args);
|
|
1681
|
+
return;
|
|
1196
1682
|
case "help":
|
|
1197
1683
|
case "--help":
|
|
1198
1684
|
case "-h":
|