@unoverse-platform/studio 0.1.90 → 0.1.91
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/local/plugin.ts +20 -12
- package/package.json +13 -3
- package/vendor/base/src/agent-mcp/discovery.ts +203 -0
- package/vendor/base/src/agent-mcp/handoff.ts +56 -0
- package/vendor/base/src/agent-mcp/index.ts +44 -0
- package/vendor/base/src/agent-mcp/invoke.ts +140 -0
- package/vendor/base/src/agent-mcp/lean.ts +111 -0
- package/vendor/base/src/agent-mcp/memoryContext.ts +104 -0
- package/vendor/base/src/agent-mcp/memoryIngest.ts +88 -0
- package/vendor/base/src/agent-mcp/rowComponents.ts +172 -0
- package/vendor/base/src/agent-mcp/tools.ts +134 -0
- package/vendor/base/src/agent-mcp/types.ts +130 -0
- package/vendor/base/src/auth/auth.ts +278 -0
- package/vendor/base/src/auth/authConfig.ts +73 -0
- package/vendor/base/src/boot.ts +245 -0
- package/vendor/base/src/definitions/definitions.ts +18 -0
- package/vendor/base/src/definitions/dsPackage.ts +45 -0
- package/vendor/base/src/definitions/fsCache.ts +128 -0
- package/vendor/base/src/definitions/index.ts +19 -0
- package/vendor/base/src/definitions/inputs.ts +30 -0
- package/vendor/base/src/definitions/load/compile/component.ts +80 -0
- package/vendor/base/src/definitions/load/compile/index.ts +12 -0
- package/vendor/base/src/definitions/load/compile/sections.ts +132 -0
- package/vendor/base/src/definitions/load/compile/template.ts +153 -0
- package/vendor/base/src/definitions/load/expand/includes.ts +27 -0
- package/vendor/base/src/definitions/load/expand/index.ts +17 -0
- package/vendor/base/src/definitions/load/expand/inherit.ts +169 -0
- package/vendor/base/src/definitions/load/expand/referenced.ts +164 -0
- package/vendor/base/src/definitions/load/expand/refs.ts +106 -0
- package/vendor/base/src/definitions/load/expand/walk.ts +91 -0
- package/vendor/base/src/definitions/load/load.ts +487 -0
- package/vendor/base/src/definitions/model.ts +238 -0
- package/vendor/base/src/definitions/registry/catalog.ts +643 -0
- package/vendor/base/src/definitions/registry/homes.ts +252 -0
- package/vendor/base/src/definitions/theme.ts +383 -0
- package/vendor/base/src/executor.ts +284 -0
- package/vendor/base/src/http/uwsFetchAdapter.ts +260 -0
- package/vendor/base/src/items/baseVersion.ts +96 -0
- package/vendor/base/src/items/collect.ts +199 -0
- package/vendor/base/src/items/fingerprint.ts +34 -0
- package/vendor/base/src/items/publish.ts +198 -0
- package/vendor/base/src/lint/context.mjs +56 -0
- package/vendor/base/src/lint/design/defs.mjs +46 -0
- package/vendor/base/src/lint/design/file.mjs +737 -0
- package/vendor/base/src/lint/design/index.d.mts +30 -0
- package/vendor/base/src/lint/design/index.mjs +996 -0
- package/vendor/base/src/lint/design/tokens.mjs +128 -0
- package/vendor/base/src/lint/design/vocabulary.mjs +181 -0
- package/vendor/base/src/lint/design/walk.mjs +444 -0
- package/vendor/base/src/lint/index.mjs +28 -0
- package/vendor/base/src/lint/nodes/blocks.mjs +63 -0
- package/vendor/base/src/lint/nodes/index.mjs +64 -0
- package/vendor/base/src/lint/nodes/load.mjs +126 -0
- package/vendor/base/src/lint/nodes/node.mjs +764 -0
- package/vendor/base/src/lint/nodes/package.mjs +112 -0
- package/vendor/base/src/lint/nodes/schema.mjs +75 -0
- package/vendor/base/src/logger.ts +33 -0
- package/vendor/base/src/manifests/compose.ts +539 -0
- package/vendor/base/src/manifests/executor/callback.ts +107 -0
- package/vendor/base/src/manifests/executor/context.ts +107 -0
- package/vendor/base/src/manifests/executor/index.ts +47 -0
- package/vendor/base/src/manifests/executor/lookup.ts +21 -0
- package/vendor/base/src/manifests/executor/memory.ts +62 -0
- package/vendor/base/src/manifests/executor/promise.ts +71 -0
- package/vendor/base/src/manifests/executor/refire.ts +34 -0
- package/vendor/base/src/manifests/executor/send.ts +65 -0
- package/vendor/base/src/manifests/executor/session.ts +46 -0
- package/vendor/base/src/manifests/executor/toolBridge.ts +266 -0
- package/vendor/base/src/manifests/index.ts +280 -0
- package/vendor/base/src/manifests/integrity.ts +93 -0
- package/vendor/base/src/manifests/rows.ts +33 -0
- package/vendor/base/src/manifests/runtime/auth/aws.ts +240 -0
- package/vendor/base/src/manifests/runtime/auth/oauth.ts +76 -0
- package/vendor/base/src/manifests/runtime/authorize.ts +159 -0
- package/vendor/base/src/manifests/runtime/channels/final.ts +171 -0
- package/vendor/base/src/manifests/runtime/channels/service.ts +125 -0
- package/vendor/base/src/manifests/runtime/channels/workflow.ts +310 -0
- package/vendor/base/src/manifests/runtime/context.ts +150 -0
- package/vendor/base/src/manifests/runtime/docstore/handlers.ts +547 -0
- package/vendor/base/src/manifests/runtime/docstore/index.ts +135 -0
- package/vendor/base/src/manifests/runtime/docstore/sectionizer.ts +184 -0
- package/vendor/base/src/manifests/runtime/docstore/store.ts +172 -0
- package/vendor/base/src/manifests/runtime/docstore/types.ts +60 -0
- package/vendor/base/src/manifests/runtime/duplex/audioLane.ts +165 -0
- package/vendor/base/src/manifests/runtime/duplex/session.ts +511 -0
- package/vendor/base/src/manifests/runtime/events.ts +223 -0
- package/vendor/base/src/manifests/runtime/helpers.ts +53 -0
- package/vendor/base/src/manifests/runtime/http/allowedHosts.ts +118 -0
- package/vendor/base/src/manifests/runtime/http/request.ts +637 -0
- package/vendor/base/src/manifests/runtime/http/response.ts +162 -0
- package/vendor/base/src/manifests/runtime/index.ts +57 -0
- package/vendor/base/src/manifests/runtime/loops/chunk.ts +98 -0
- package/vendor/base/src/manifests/runtime/loops/loop.ts +191 -0
- package/vendor/base/src/manifests/runtime/loops/paginate.ts +143 -0
- package/vendor/base/src/manifests/runtime/loops/poll.ts +169 -0
- package/vendor/base/src/manifests/runtime/narrate.ts +53 -0
- package/vendor/base/src/manifests/runtime/state.ts +171 -0
- package/vendor/base/src/manifests/runtime/templating.ts +110 -0
- package/vendor/base/src/manifests/runtime/tools/regionSkills.ts +125 -0
- package/vendor/base/src/manifests/runtime/tools/toolloop.ts +542 -0
- package/vendor/base/src/manifests/runtime/usage.ts +87 -0
- package/vendor/base/src/manifests/source.ts +187 -0
- package/vendor/base/src/manifests/watch.ts +67 -0
- package/vendor/base/src/markdown/document.ts +141 -0
- package/vendor/base/src/paths.ts +180 -0
- package/vendor/base/src/platform/audioManager.ts +100 -0
- package/vendor/base/src/platform/clientTransport.ts +65 -0
- package/vendor/base/src/platform/executionContext.ts +154 -0
- package/vendor/base/src/platform/index.ts +10 -0
- package/vendor/base/src/platform/pluginAPI.ts +146 -0
- package/vendor/base/src/platform/redis.ts +54 -0
- package/vendor/base/src/platform/serviceCalls.ts +222 -0
- package/vendor/base/src/pluginBase.ts +248 -0
- package/vendor/base/src/plugins/README.md +57 -0
- package/vendor/base/src/plugins/discovery.ts +87 -0
- package/vendor/base/src/plugins/index.ts +16 -0
- package/vendor/base/src/plugins/loader.ts +179 -0
- package/vendor/base/src/plugins/redis.ts +36 -0
- package/vendor/base/src/plugins/state.ts +53 -0
- package/vendor/base/src/registry.ts +95 -0
- package/vendor/base/src/security/permissions.ts +93 -0
- package/vendor/base/src/security/rules.ts +73 -0
- package/vendor/base/src/template/SafeExpression.ts +556 -0
- package/vendor/base/src/template/StringTemplateResolver.ts +75 -0
- package/vendor/base/src/template/index.ts +22 -0
- package/vendor/base/src/types.ts +50 -0
- package/vendor/base/src/workflow/GetMemoryService.ts +158 -0
- package/vendor/base/src/workflow/PlatformMemoryCore.ts +438 -0
- package/vendor/base/src/workflow/routing.ts +122 -0
- package/vendor/sdk/lib/core/store.ts +26 -6
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node Registry - In-memory storage for loaded node definitions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { NodeDefinition } from "./types.js";
|
|
6
|
+
import { boot } from "./boot.js";
|
|
7
|
+
|
|
8
|
+
// In-memory node registry (loaded from packages)
|
|
9
|
+
const nodeRegistry: Map<string, NodeDefinition> = new Map();
|
|
10
|
+
|
|
11
|
+
// In-memory credential type registry (loaded from packages)
|
|
12
|
+
const credentialRegistry: Map<string, any> = new Map();
|
|
13
|
+
|
|
14
|
+
// Track which plugins are loaded and their node types
|
|
15
|
+
const pluginNodeMap: Map<string, string[]> = new Map();
|
|
16
|
+
|
|
17
|
+
export function getNodeRegistry(): Map<string, NodeDefinition> {
|
|
18
|
+
return nodeRegistry;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getPluginNodeMap(): Map<string, string[]> {
|
|
22
|
+
return pluginNodeMap;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getNode(nodeType: string): NodeDefinition | undefined {
|
|
26
|
+
return nodeRegistry.get(nodeType);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function setNode(nodeType: string, definition: NodeDefinition): void {
|
|
30
|
+
nodeRegistry.set(nodeType, definition);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function deleteNode(nodeType: string): boolean {
|
|
34
|
+
return nodeRegistry.delete(nodeType);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function clearRegistry(): void {
|
|
38
|
+
nodeRegistry.clear();
|
|
39
|
+
pluginNodeMap.clear();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getNodeCount(): number {
|
|
43
|
+
return nodeRegistry.size;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function getPluginCount(): number {
|
|
47
|
+
return pluginNodeMap.size;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function getAllNodes(): NodeDefinition[] {
|
|
51
|
+
return Array.from(nodeRegistry.values());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function getAllNodeTypes(): string[] {
|
|
55
|
+
return Array.from(nodeRegistry.keys());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Credential registry functions
|
|
59
|
+
export function getCredentialRegistry(): Map<string, any> {
|
|
60
|
+
return credentialRegistry;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function registerCredentialType(credentialType: any): void {
|
|
64
|
+
if (credentialRegistry.has(credentialType.name)) {
|
|
65
|
+
// Shared on purpose (several AWS packages declare awsCredential), so first-wins is
|
|
66
|
+
// the design and not an incident. Counted, not announced.
|
|
67
|
+
boot.credential(credentialType.name, true);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
credentialRegistry.set(credentialType.name, credentialType);
|
|
71
|
+
boot.credential(credentialType.name);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function getAllCredentialTypes(): any[] {
|
|
75
|
+
return Array.from(credentialRegistry.values());
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Replace a credential type outright, for owners that can be RELOADED.
|
|
80
|
+
*
|
|
81
|
+
* registerCredentialType is deliberately first-wins-and-skip, which is right for code
|
|
82
|
+
* plugins: they load once and a duplicate is a mistake. A declarative manifest is
|
|
83
|
+
* different — it can be installed, updated and removed while the process runs, and a
|
|
84
|
+
* package that adds a field to its credential must be able to land that change. With
|
|
85
|
+
* only the skipping path, the old shape would win forever and the new field would
|
|
86
|
+
* never appear in the UI or reach decryption.
|
|
87
|
+
*/
|
|
88
|
+
export function replaceCredentialType(credentialType: any): void {
|
|
89
|
+
credentialRegistry.set(credentialType.name, credentialType);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Retract a credential type. Returns false if it was not registered. */
|
|
93
|
+
export function unregisterCredentialType(name: string): boolean {
|
|
94
|
+
return credentialRegistry.delete(name);
|
|
95
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE VOCABULARY. Every question this platform's code is allowed to ask about a caller.
|
|
3
|
+
*
|
|
4
|
+
* Permissions are named after the CAPABILITY, never the person, and that is the whole
|
|
5
|
+
* discipline: a gate asks "may you manage credentials", not "are you an admin". The two
|
|
6
|
+
* read the same today, with two roles, and they diverge the first time somebody needs one
|
|
7
|
+
* capability without the whole role. Then the question the code asks is still right and
|
|
8
|
+
* only the grant table below moves.
|
|
9
|
+
*
|
|
10
|
+
* SHARED BY EVERY SERVICE. This lives in `base` rather than in one server because the
|
|
11
|
+
* unoverse server and apps/memory must not be able to disagree about what `admin` means.
|
|
12
|
+
* They did: memory derived its own `isAdmin` from the role string while the unoverse server
|
|
13
|
+
* had no role check at all, so "who is an administrator" had two answers and one of them was
|
|
14
|
+
* "nobody". One map, imported twice.
|
|
15
|
+
*
|
|
16
|
+
* ROLES ARE THE TOKEN'S SHAPE; PERMISSIONS ARE THE CODE'S SHAPE. Auth0 issues this
|
|
17
|
+
* platform `roles: ["admin"]` / `["author"]`, and the tokens carry no permissions claim we
|
|
18
|
+
* can rely on. So `ROLE_GRANTS` is the bridge, and it is deliberately the ONLY place the
|
|
19
|
+
* two vocabularies meet: one map, read once, so switching to real permission claims later
|
|
20
|
+
* is an edit here rather than a hunt through routes.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** Every capability. Adding one here is how a new kind of access gets invented. */
|
|
24
|
+
export const PERMISSIONS = [
|
|
25
|
+
/**
|
|
26
|
+
* Build. Workflows (create, edit, delete, deploy, toggle, snapshots), the builder MCP,
|
|
27
|
+
* spatial and content, the node registry, and taking items from the marketplace.
|
|
28
|
+
*
|
|
29
|
+
* ONE PERMISSION, NOT FIVE, by owner ruling 2026-08-31: spatial and plugin management are
|
|
30
|
+
* things a builder does in the course of building, not separate privileges. Splitting them
|
|
31
|
+
* would have produced a table nobody could hold in their head and no decision it could
|
|
32
|
+
* express that this cannot.
|
|
33
|
+
*/
|
|
34
|
+
"workflow:author",
|
|
35
|
+
|
|
36
|
+
/** Push your own assets into this universe (`POST /publish`). Existing, already enforced. */
|
|
37
|
+
"marketplace:publish",
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Read and write the credential store. THE SENSITIVE ONE, and the reason the two roles
|
|
41
|
+
* are not the same role: an author can build anything on this platform and still cannot
|
|
42
|
+
* see an API key.
|
|
43
|
+
*/
|
|
44
|
+
"credentials:manage",
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Read ANOTHER person's memory (apps/memory). The second sensitive one, and it belongs
|
|
48
|
+
* beside the first: both are "may you see something that is not yours".
|
|
49
|
+
*
|
|
50
|
+
* The memory service enforces this differently from every route above, and correctly so.
|
|
51
|
+
* Its routes are not capability-gated, they are USER-SCOPED: everyone reaches the same
|
|
52
|
+
* endpoints and `enforceUserScope` narrows the answer to your own id. This permission is
|
|
53
|
+
* what widens it. A route table would be the wrong shape for that, which is why memory
|
|
54
|
+
* shares this vocabulary and not the policy table.
|
|
55
|
+
*/
|
|
56
|
+
"memory:read-all",
|
|
57
|
+
] as const;
|
|
58
|
+
|
|
59
|
+
export type Permission = (typeof PERMISSIONS)[number];
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* WHICH ROLE GRANTS WHAT. The one bridge between Auth0's vocabulary and this code's.
|
|
63
|
+
*
|
|
64
|
+
* `admin` is `author` plus the secrets. That is the entire difference between them, stated
|
|
65
|
+
* as data rather than described in a comment somewhere, so it cannot drift.
|
|
66
|
+
*/
|
|
67
|
+
const AUTHOR_GRANTS: Permission[] = ["workflow:author", "marketplace:publish"];
|
|
68
|
+
|
|
69
|
+
export const ROLE_GRANTS: Record<string, readonly Permission[]> = {
|
|
70
|
+
author: AUTHOR_GRANTS,
|
|
71
|
+
admin: [...AUTHOR_GRANTS, "credentials:manage", "memory:read-all"],
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* What this caller may do, from the roles on their token.
|
|
76
|
+
*
|
|
77
|
+
* A PERMISSION CLAIM WINS WHEN IT IS THERE. If a deployment does turn on Auth0's
|
|
78
|
+
* "add permissions to the access token", those permissions are honoured directly and the
|
|
79
|
+
* role map becomes a fallback rather than a translation layer. Nothing needs to change here
|
|
80
|
+
* on the day that happens, which is the point of reading both.
|
|
81
|
+
*/
|
|
82
|
+
export function grantedTo(user: { roles?: string[]; permissions?: string[] } | undefined): Set<Permission> {
|
|
83
|
+
const granted = new Set<Permission>();
|
|
84
|
+
if (!user) return granted;
|
|
85
|
+
|
|
86
|
+
for (const p of user.permissions ?? []) {
|
|
87
|
+
if ((PERMISSIONS as readonly string[]).includes(p)) granted.add(p as Permission);
|
|
88
|
+
}
|
|
89
|
+
for (const role of user.roles ?? []) {
|
|
90
|
+
for (const p of ROLE_GRANTS[role] ?? []) granted.add(p);
|
|
91
|
+
}
|
|
92
|
+
return granted;
|
|
93
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SHAPE of an access rule, and how a request is matched against a list of them.
|
|
3
|
+
*
|
|
4
|
+
* No table here. Each service owns its own routes and therefore its own table
|
|
5
|
+
* (`apps/unoverse/server/src/security/policy.ts`); this is the vocabulary they are written
|
|
6
|
+
* in, so the matching semantics cannot drift between services.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Permission } from "./permissions.js";
|
|
10
|
+
|
|
11
|
+
export type Requirement = "public" | "signed-in" | "own-gate" | Permission;
|
|
12
|
+
|
|
13
|
+
export interface Rule {
|
|
14
|
+
/** HTTP methods this rule covers. `*` is every method. */
|
|
15
|
+
methods: readonly string[] | "*";
|
|
16
|
+
/** Path, with `:param` for one segment and a trailing `*` for a prefix. */
|
|
17
|
+
path: string;
|
|
18
|
+
requires: Requirement;
|
|
19
|
+
/** Why. Required for `public` and `own-gate`, because both need a reason to be read. */
|
|
20
|
+
note?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const ALL = "*" as const;
|
|
24
|
+
export const READ = ["GET", "HEAD"] as const;
|
|
25
|
+
export const WRITE = ["POST", "PUT", "PATCH", "DELETE"] as const;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The rule for one request, or null if this table does not cover it.
|
|
29
|
+
*
|
|
30
|
+
* FIRST MATCH WINS, so order is meaning: the specific lines sit above `/api/*`, and moving
|
|
31
|
+
* that catch-all upwards would silently demote everything under it to "signed-in". The
|
|
32
|
+
* ordering test in tests/security guards exactly that.
|
|
33
|
+
*
|
|
34
|
+
* A null return is NOT permission. The caller refuses it (gate.ts) and the coverage test
|
|
35
|
+
* fails the build, so an unlisted route is caught before it ships and denied if it ever is.
|
|
36
|
+
*/
|
|
37
|
+
export function ruleFor(policy: readonly Rule[], method: string, pathname: string): Rule | null {
|
|
38
|
+
for (const rule of policy) {
|
|
39
|
+
if (rule.methods !== ALL && !rule.methods.includes(method.toUpperCase())) continue;
|
|
40
|
+
if (matches(rule.path, pathname)) return rule;
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The value a `:userId` segment matched, or undefined when the pattern has none.
|
|
47
|
+
*
|
|
48
|
+
* A path that names a user is a path that can be pointed at somebody else. Rather than each
|
|
49
|
+
* handler remembering to compare, the TABLE carries the rule: write `:userId` in the pattern
|
|
50
|
+
* and the gate enforces that it is the caller's own (gate.ts). The engine handlers stay
|
|
51
|
+
* unchanged and cannot forget, because they are never consulted.
|
|
52
|
+
*/
|
|
53
|
+
export function ownerOf(pattern: string, pathname: string): string | undefined {
|
|
54
|
+
const p = pattern.split("/").filter(Boolean);
|
|
55
|
+
const s = (pathname.split("?")[0] ?? "").split("/").filter(Boolean);
|
|
56
|
+
const at = p.indexOf(":userId");
|
|
57
|
+
return at >= 0 ? s[at] : undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** `:param` matches one segment; a trailing `*` matches the rest, including nothing. */
|
|
61
|
+
function matches(pattern: string, pathname: string): boolean {
|
|
62
|
+
const path = pathname.split("?")[0].replace(/\/+$/, "") || "/";
|
|
63
|
+
const p = pattern.split("/").filter(Boolean);
|
|
64
|
+
const s = path.split("/").filter(Boolean);
|
|
65
|
+
|
|
66
|
+
for (let i = 0; i < p.length; i++) {
|
|
67
|
+
if (p[i] === "*") return true; // trailing wildcard: everything from here down
|
|
68
|
+
if (i >= s.length) return false;
|
|
69
|
+
if (p[i].startsWith(":")) continue; // one segment, any value
|
|
70
|
+
if (p[i] !== s[i]) return false;
|
|
71
|
+
}
|
|
72
|
+
return p.length === s.length;
|
|
73
|
+
}
|